@wrongstack/core 0.313.0 → 0.313.1
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/dist/chronicle/index.js +84 -6391
- package/dist/chronicle/project-server.js +84 -4868
- package/dist/chunk-2BUUIMQB.js +225 -0
- package/dist/chunk-2FK4WTFF.js +207 -0
- package/dist/chunk-2FQL4PDC.js +292 -0
- package/dist/chunk-2KKODIQU.js +2256 -0
- package/dist/chunk-2YU4WVBW.js +134 -0
- package/dist/chunk-3E244UDA.js +356 -0
- package/dist/chunk-3XPVB7BA.js +135 -0
- package/dist/chunk-4LJJYBAU.js +388 -0
- package/dist/chunk-4VWO3ZXL.js +342 -0
- package/dist/chunk-4XV6IR37.js +187 -0
- package/dist/chunk-4ZHTKJHM.js +183 -0
- package/dist/chunk-52RTVH4M.js +361 -0
- package/dist/chunk-57GEWK5X.js +26 -0
- package/dist/chunk-5DSMEVA4.js +931 -0
- package/dist/chunk-5RP3QTRL.js +1223 -0
- package/dist/chunk-5S437PRC.js +50 -0
- package/dist/chunk-5XDUGTKN.js +511 -0
- package/dist/chunk-62V2AFVG.js +422 -0
- package/dist/chunk-65RDIOR2.js +2470 -0
- package/dist/chunk-6AKOTGLM.js +597 -0
- package/dist/chunk-7ZHZE5EO.js +28 -0
- package/dist/chunk-A455ATZB.js +140 -0
- package/dist/chunk-AD7ZFZCS.js +46 -0
- package/dist/chunk-AI4LLQ2U.js +856 -0
- package/dist/chunk-ASX6AW36.js +789 -0
- package/dist/chunk-AT4V4XGI.js +3522 -0
- package/dist/chunk-AWNQ6ED6.js +311 -0
- package/dist/chunk-B4AMLZPB.js +9 -0
- package/dist/chunk-BAKVJLEV.js +417 -0
- package/dist/chunk-BJHVULHO.js +55 -0
- package/dist/chunk-CB2HFKTP.js +9 -0
- package/dist/chunk-CE4ZOCOI.js +1640 -0
- package/dist/chunk-CQQL4ZOQ.js +151 -0
- package/dist/chunk-DEIQMBD2.js +65 -0
- package/dist/chunk-E6I5RXVK.js +58 -0
- package/dist/chunk-EFEHDQGY.js +1589 -0
- package/dist/chunk-EODDVJBM.js +82 -0
- package/dist/chunk-EPC3GZD4.js +308 -0
- package/dist/chunk-FAYRX2AD.js +13 -0
- package/dist/chunk-FFSX2QDQ.js +362 -0
- package/dist/chunk-FHAMQS7Q.js +5856 -0
- package/dist/chunk-FIV4IJL7.js +2263 -0
- package/dist/chunk-FNXVT7HF.js +52 -0
- package/dist/chunk-FSWCWSTS.js +395 -0
- package/dist/chunk-FW5LDDMJ.js +16464 -0
- package/dist/chunk-FXQLIX3K.js +211 -0
- package/dist/chunk-G5AAKEEZ.js +228 -0
- package/dist/chunk-G5S3S5ZN.js +38 -0
- package/dist/chunk-GAZEWQEZ.js +231 -0
- package/dist/chunk-GFJOMSHG.js +125 -0
- package/dist/chunk-GFXZTHNI.js +158 -0
- package/dist/chunk-GM54GJBU.js +960 -0
- package/dist/chunk-GPQ3NJPR.js +900 -0
- package/dist/chunk-GTCU6DZ3.js +1 -0
- package/dist/chunk-H5E77KUJ.js +15 -0
- package/dist/chunk-H72H4XDU.js +1023 -0
- package/dist/chunk-HA4ZYA6T.js +178 -0
- package/dist/chunk-I45KAW2N.js +5020 -0
- package/dist/chunk-IQ35HMQL.js +1396 -0
- package/dist/chunk-IYFBFX34.js +86 -0
- package/dist/chunk-JJPMWPB6.js +109 -0
- package/dist/chunk-JTHKOYWU.js +663 -0
- package/dist/chunk-JVGPR3TS.js +365 -0
- package/dist/chunk-JVKB2TBL.js +48 -0
- package/dist/chunk-JX7VBZDK.js +31 -0
- package/dist/chunk-K3G6JBUC.js +5232 -0
- package/dist/chunk-K4YUNKU7.js +589 -0
- package/dist/chunk-KBZGE6LN.js +710 -0
- package/dist/chunk-KDQJCT2J.js +45 -0
- package/dist/chunk-KI4L2DUL.js +560 -0
- package/dist/chunk-KJ2ZTQSL.js +47 -0
- package/dist/chunk-KSJ5UTP7.js +154 -0
- package/dist/chunk-LHOA5DWA.js +28 -0
- package/dist/chunk-LX5QWLBL.js +27 -0
- package/dist/chunk-LZDDLNCZ.js +279 -0
- package/dist/chunk-M2WF6AXG.js +115 -0
- package/dist/chunk-M7BS2K6U.js +1 -0
- package/dist/chunk-NDBIPFNY.js +102 -0
- package/dist/chunk-NEMIWEXC.js +1199 -0
- package/dist/chunk-NFAYHPRH.js +323 -0
- package/dist/chunk-O37BQ65S.js +21 -0
- package/dist/chunk-O74MJHXU.js +187 -0
- package/dist/chunk-OJZEX73F.js +112 -0
- package/dist/chunk-PAAWLLZX.js +74 -0
- package/dist/chunk-PFXFQQRT.js +339 -0
- package/dist/chunk-PFZO3C6H.js +14 -0
- package/dist/chunk-PHH7LD45.js +113 -0
- package/dist/chunk-QRTG4XTI.js +188 -0
- package/dist/chunk-QVOVMXJR.js +1351 -0
- package/dist/chunk-RCQYT2I4.js +757 -0
- package/dist/chunk-RD6DEE36.js +1178 -0
- package/dist/chunk-RJIZEJ42.js +30 -0
- package/dist/chunk-RPOA7IAE.js +150 -0
- package/dist/chunk-SN2DQRL6.js +1172 -0
- package/dist/chunk-SQJVFEBV.js +41 -0
- package/dist/chunk-SQP64RCS.js +5391 -0
- package/dist/chunk-SVT4RNTR.js +49 -0
- package/dist/chunk-SWUFN7XM.js +43 -0
- package/dist/chunk-SXMVYC5H.js +174 -0
- package/dist/chunk-SZ6AYUIZ.js +23 -0
- package/dist/chunk-T3KGZBU5.js +3171 -0
- package/dist/chunk-TBRL2RCC.js +9 -0
- package/dist/chunk-TRT2HRWZ.js +18 -0
- package/dist/chunk-TYW2GI5I.js +1077 -0
- package/dist/chunk-UIWONWVG.js +4959 -0
- package/dist/chunk-VB3H5WWB.js +151 -0
- package/dist/chunk-W4A6QA3D.js +258 -0
- package/dist/chunk-WBPVKEEE.js +227 -0
- package/dist/chunk-WEBMSQP2.js +123 -0
- package/dist/chunk-WOEZV3Q4.js +333 -0
- package/dist/chunk-WQQON3SV.js +473 -0
- package/dist/chunk-WQTOVMGV.js +119 -0
- package/dist/chunk-WZO23AWO.js +2779 -0
- package/dist/chunk-XBDP4QPW.js +7 -0
- package/dist/chunk-XEBHSJLT.js +103 -0
- package/dist/chunk-XQ2ECNK3.js +1 -0
- package/dist/chunk-XQ2XGIBF.js +2086 -0
- package/dist/chunk-Y2GE5TSI.js +355 -0
- package/dist/chunk-YBUJCZ57.js +351 -0
- package/dist/chunk-YNCVSFGF.js +149 -0
- package/dist/chunk-YUPMXOGP.js +282 -0
- package/dist/chunk-YY6APYUM.js +49 -0
- package/dist/chunk-Z2IJUO45.js +691 -0
- package/dist/chunk-ZEHUCJHY.js +129 -0
- package/dist/chunk-ZYU2YOJ6.js +1496 -0
- package/dist/chunk-ZZ4V3WIZ.js +163 -0
- package/dist/coordination/agents/index.js +145 -6260
- package/dist/coordination/index.js +365 -34324
- package/dist/coordination/mailbox-project-server.js +48 -3276
- package/dist/coordination/provider-status-tracker.d.ts +4 -0
- package/dist/core/agent.d.ts +14 -1
- package/dist/core/index.js +267 -12480
- package/dist/core/model-ref.js +5 -23
- package/dist/core/provider-runner.d.ts +36 -1
- package/dist/design/index.js +50 -1087
- package/dist/execution/index.d.ts +1 -1
- package/dist/execution/index.js +1599 -21888
- package/dist/execution/prompt-enhancer.js +16 -535
- package/dist/execution/retry-policy.d.ts +31 -1
- package/dist/extension/index.js +7 -311
- package/dist/goal/index.js +33 -3435
- package/dist/hooks/index.js +14 -1138
- package/dist/hq/index.js +112 -5139
- package/dist/hq/protocol.js +21 -668
- package/dist/index.js +1410 -92656
- package/dist/infrastructure/index.js +64 -4811
- package/dist/kernel/events/provider-events.d.ts +15 -0
- package/dist/kernel/index.js +14 -992
- package/dist/models/index.js +40 -1117
- package/dist/notifications/index.js +3 -108
- package/dist/observability/index.js +17 -279
- package/dist/plugin/index.js +584 -14835
- package/dist/prompts/index.js +14 -948
- package/dist/registry/index.js +9 -822
- package/dist/registry-R23UBE7H.js +14 -0
- package/dist/replay/index.js +6 -84
- package/dist/review-finding-store-DKAV3AS6.js +21 -0
- package/dist/review-report-store-OWY3EC3N.js +19 -0
- package/dist/security/index.js +114 -2202
- package/dist/session-catalog/index.js +36 -2103
- package/dist/session-catalog/project-server.js +42 -1435
- package/dist/skills/index.js +35 -1578
- package/dist/storage/index.js +182 -15600
- package/dist/tasking/index.js +10 -676
- package/dist/tools/index.js +52 -9590
- package/dist/types/config/autonomy.d.ts +5 -0
- package/dist/types/config/ui.d.ts +1 -1
- package/dist/types/errors.d.ts +18 -0
- package/dist/types/index.js +121 -1342
- package/dist/types/provider-runner.d.ts +8 -0
- package/dist/types/session-markers.js +8 -144
- package/dist/utils/error.js +3 -4
- package/dist/utils/expect-defined.js +3 -9
- package/dist/utils/heap-watchdog.js +6 -706
- package/dist/utils/index.js +269 -5685
- package/dist/utils/sage-output-block.js +5 -42
- package/dist/wiring/proxy-rewrite.d.ts +18 -0
- package/dist/wiring/proxy-rewrite.js +18 -121
- package/dist/worktree/index.js +6 -887
- package/package.json +4 -4
package/dist/design/index.js
CHANGED
|
@@ -1,1076 +1,39 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
import
|
|
36
|
-
import
|
|
37
|
-
|
|
38
|
-
// src/skills/frontmatter.ts
|
|
39
|
-
function stripFrontmatter(raw) {
|
|
40
|
-
const text = normalizeLineEndings(raw);
|
|
41
|
-
if (!text.startsWith("---")) return text;
|
|
42
|
-
const end = text.indexOf("\n---", 4);
|
|
43
|
-
if (end === -1) return text;
|
|
44
|
-
let body = text.slice(end + 4);
|
|
45
|
-
if (body.startsWith("\n")) body = body.slice(1);
|
|
46
|
-
return body;
|
|
47
|
-
}
|
|
48
|
-
function normalizeLineEndings(s) {
|
|
49
|
-
return s.replace(/\r\n?/g, "\n");
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// src/utils/wstack-paths.ts
|
|
53
|
-
import { createHash } from "node:crypto";
|
|
54
|
-
import * as fs from "node:fs";
|
|
55
|
-
import * as os from "node:os";
|
|
56
|
-
import * as path from "node:path";
|
|
57
|
-
function canonicalProjectRoot(absRoot) {
|
|
58
|
-
const checkoutRoot = path.resolve(absRoot);
|
|
59
|
-
const dotGit = path.join(checkoutRoot, ".git");
|
|
60
|
-
try {
|
|
61
|
-
if (!fs.statSync(dotGit).isFile()) return checkoutRoot;
|
|
62
|
-
const gitDirLine = fs.readFileSync(dotGit, "utf8").trim();
|
|
63
|
-
const match = /^gitdir:\s*(.+)$/i.exec(gitDirLine);
|
|
64
|
-
if (!match?.[1]) return checkoutRoot;
|
|
65
|
-
const gitDir = path.resolve(checkoutRoot, match[1].trim());
|
|
66
|
-
const commonDirFile = path.join(gitDir, "commondir");
|
|
67
|
-
if (!fs.statSync(commonDirFile).isFile()) return checkoutRoot;
|
|
68
|
-
const commonDir = path.resolve(gitDir, fs.readFileSync(commonDirFile, "utf8").trim());
|
|
69
|
-
const worktreesDir = path.dirname(gitDir);
|
|
70
|
-
if (path.basename(worktreesDir).toLowerCase() !== "worktrees") return checkoutRoot;
|
|
71
|
-
if (path.resolve(worktreesDir, "..") !== commonDir) return checkoutRoot;
|
|
72
|
-
if (path.basename(commonDir).toLowerCase() !== ".git") return checkoutRoot;
|
|
73
|
-
return path.dirname(commonDir);
|
|
74
|
-
} catch {
|
|
75
|
-
return checkoutRoot;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
function projectHash(absRoot) {
|
|
79
|
-
return createHash("sha256").update(canonicalProjectRoot(absRoot)).digest("hex").slice(0, 12);
|
|
80
|
-
}
|
|
81
|
-
function projectSlug(absRoot) {
|
|
82
|
-
const identityRoot = canonicalProjectRoot(absRoot);
|
|
83
|
-
const base = slugify(path.basename(identityRoot));
|
|
84
|
-
const hash = createHash("sha256").update(identityRoot).digest("hex").slice(0, 6);
|
|
85
|
-
return `${base}-${hash}`;
|
|
86
|
-
}
|
|
87
|
-
function slugify(name) {
|
|
88
|
-
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "project";
|
|
89
|
-
}
|
|
90
|
-
function safeProfileName(name) {
|
|
91
|
-
const safe = (name ?? "").replace(/[/\\:]/g, "_").replace(/\.\./g, "_").trim();
|
|
92
|
-
return safe || "default";
|
|
93
|
-
}
|
|
94
|
-
function bootstrapProfileName(globalRoot) {
|
|
95
|
-
try {
|
|
96
|
-
const parsed = JSON.parse(fs.readFileSync(path.join(globalRoot, "config.json"), "utf8"));
|
|
97
|
-
return safeProfileName(
|
|
98
|
-
typeof parsed.activeProfile === "string" ? parsed.activeProfile : void 0
|
|
99
|
-
);
|
|
100
|
-
} catch {
|
|
101
|
-
return "default";
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
function wstackGlobalRoot() {
|
|
105
|
-
const fromEnv = process.env["WRONGSTACK_HOME"];
|
|
106
|
-
if (fromEnv && fromEnv.trim().length > 0) return path.resolve(fromEnv);
|
|
107
|
-
return path.join(os.homedir(), ".wrongstack");
|
|
108
|
-
}
|
|
109
|
-
function resolveWstackPaths(opts) {
|
|
110
|
-
const globalRoot = opts.globalRoot ?? (opts.userHome ? path.join(opts.userHome, ".wrongstack") : wstackGlobalRoot());
|
|
111
|
-
const homeDir = opts.userHome ?? os.homedir();
|
|
112
|
-
const profileName = safeProfileName(opts.profileName ?? bootstrapProfileName(globalRoot));
|
|
113
|
-
const profileDir = path.join(globalRoot, "profiles", profileName);
|
|
114
|
-
const hash = projectHash(opts.projectRoot);
|
|
115
|
-
const slug = projectSlug(opts.projectRoot);
|
|
116
|
-
const projectDir = path.join(globalRoot, "projects", slug);
|
|
117
|
-
return {
|
|
118
|
-
globalRoot,
|
|
119
|
-
profileName,
|
|
120
|
-
profileDir,
|
|
121
|
-
projectRoot: opts.projectRoot,
|
|
122
|
-
homeDir,
|
|
123
|
-
configDir: profileDir,
|
|
124
|
-
globalConfig: path.join(globalRoot, "config.json"),
|
|
125
|
-
profilesDir: path.join(globalRoot, "profiles"),
|
|
126
|
-
profileConfig: (name) => {
|
|
127
|
-
return path.join(globalRoot, "profiles", safeProfileName(name), "config.json");
|
|
128
|
-
},
|
|
129
|
-
profileStatuslineConfig: (name) => {
|
|
130
|
-
return path.join(globalRoot, "profiles", safeProfileName(name), "statusline.json");
|
|
131
|
-
},
|
|
132
|
-
profileModeConfig: (name) => {
|
|
133
|
-
return path.join(globalRoot, "profiles", safeProfileName(name), "mode.json");
|
|
134
|
-
},
|
|
135
|
-
profileProviderStatus: (name) => {
|
|
136
|
-
return path.join(globalRoot, "profiles", safeProfileName(name), "provider-status.json");
|
|
137
|
-
},
|
|
138
|
-
profileUpdateCache: (name) => {
|
|
139
|
-
return path.join(globalRoot, "profiles", safeProfileName(name), "update-cache.json");
|
|
140
|
-
},
|
|
141
|
-
secretsKey: path.join(globalRoot, ".key"),
|
|
142
|
-
globalMemory: path.join(profileDir, "memory.md"),
|
|
143
|
-
globalSkills: path.join(profileDir, "skills"),
|
|
144
|
-
globalClaudeSkills: path.join(homeDir, ".claude", "skills"),
|
|
145
|
-
globalDesignKits: path.join(profileDir, "design-kits"),
|
|
146
|
-
globalPrompts: path.join(profileDir, "prompts"),
|
|
147
|
-
globalInstructions: path.join(profileDir, "instructions"),
|
|
148
|
-
promptUsage: path.join(profileDir, "prompt-usage.json"),
|
|
149
|
-
cacheDir: path.join(globalRoot, "cache"),
|
|
150
|
-
modelsCache: path.join(globalRoot, "cache", "models.dev.json"),
|
|
151
|
-
modelsOverlayCache: path.join(globalRoot, "cache", "models-overlay.json"),
|
|
152
|
-
historyFile: path.join(profileDir, "history"),
|
|
153
|
-
logFile: path.join(globalRoot, "logs", "wrongstack.log"),
|
|
154
|
-
projectDir,
|
|
155
|
-
projectCodebaseIndex: path.join(projectDir, "codebase-index"),
|
|
156
|
-
projectMemory: path.join(projectDir, "memory.md"),
|
|
157
|
-
projectSessions: path.join(projectDir, "sessions"),
|
|
158
|
-
projectTrust: path.join(projectDir, "trust.json"),
|
|
159
|
-
projectMeta: path.join(projectDir, "meta.json"),
|
|
160
|
-
projectLocalConfig: path.join(projectDir, "config.local.json"),
|
|
161
|
-
inProjectConfig: path.join(opts.projectRoot, ".wrongstack", "config.json"),
|
|
162
|
-
inProjectAgentsFile: path.join(opts.projectRoot, ".wrongstack", "AGENTS.md"),
|
|
163
|
-
inProjectSkills: path.join(opts.projectRoot, ".wrongstack", "skills"),
|
|
164
|
-
inProjectClaudeSkills: path.join(opts.projectRoot, ".claude", "skills"),
|
|
165
|
-
inProjectPrompts: path.join(opts.projectRoot, ".wrongstack", "prompts"),
|
|
166
|
-
inProjectInstructions: path.join(opts.projectRoot, ".wrongstack", "instructions"),
|
|
167
|
-
inProjectDesignKits: path.join(opts.projectRoot, ".wrongstack", "design-kits"),
|
|
168
|
-
inProjectWorktrees: path.join(opts.projectRoot, ".wrongstack", "worktrees"),
|
|
169
|
-
projectHash: hash,
|
|
170
|
-
projectSlug: slug,
|
|
171
|
-
projectGoal: path.join(projectDir, "goal.json"),
|
|
172
|
-
projectInputHistory: path.join(projectDir, "input-history.json"),
|
|
173
|
-
projectSpecs: path.join(projectDir, "specs"),
|
|
174
|
-
projectTaskGraphs: path.join(projectDir, "task-graphs"),
|
|
175
|
-
projectSddSession: path.join(projectDir, "sdd-session.json"),
|
|
176
|
-
projectPlan: path.join(projectDir, "plan.json"),
|
|
177
|
-
projectAutophase: path.join(projectDir, "autophase"),
|
|
178
|
-
projectSddBoards: path.join(projectDir, "sdd-boards"),
|
|
179
|
-
projectRequirementIntakes: path.join(projectDir, "requirement-intakes"),
|
|
180
|
-
syncConfig: path.join(profileDir, "sync.json"),
|
|
181
|
-
configHistoryDir: path.join(globalRoot, "config-history"),
|
|
182
|
-
projectStatus: (statusProjectSlug) => {
|
|
183
|
-
if (!/^[a-z0-9](?:[a-z0-9-]{0,39})-[a-f0-9]{6}$/.test(statusProjectSlug)) {
|
|
184
|
-
throw new Error(`Invalid project slug: ${statusProjectSlug}`);
|
|
185
|
-
}
|
|
186
|
-
return path.join(globalRoot, "projects", statusProjectSlug, "status.json");
|
|
187
|
-
}
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// src/execution/design-kit-loader.ts
|
|
192
|
-
var KIT_FILE = "KIT.md";
|
|
193
|
-
var TOKENS_FILE = "tokens.json";
|
|
194
|
-
var FOUNDATIONS_ID = "_foundations";
|
|
195
|
-
function parseList(value) {
|
|
196
|
-
const trimmed = value.trim();
|
|
197
|
-
const inner = trimmed.startsWith("[") && trimmed.endsWith("]") ? trimmed.slice(1, -1) : trimmed;
|
|
198
|
-
return inner.split(",").map((s) => s.trim().replace(/^["']|["']$/g, "")).filter(Boolean);
|
|
199
|
-
}
|
|
200
|
-
function parseKitFrontmatter(raw) {
|
|
201
|
-
const out = { tags: [], stacks: [], themes: [] };
|
|
202
|
-
if (!raw.startsWith("---")) return out;
|
|
203
|
-
const end = raw.indexOf("\n---", 4);
|
|
204
|
-
if (end === -1) return out;
|
|
205
|
-
const block = raw.slice(4, end);
|
|
206
|
-
const lines = block.split("\n");
|
|
207
|
-
let listKey = null;
|
|
208
|
-
const setScalar = (key, value) => {
|
|
209
|
-
const v = value.trim().replace(/^["']|["']$/g, "");
|
|
210
|
-
if (key === "id") out.id = v;
|
|
211
|
-
else if (key === "name") out.name = v;
|
|
212
|
-
else if (key === "aesthetic") out.aesthetic = v;
|
|
213
|
-
else if (key === "bestFor") out.bestFor = v;
|
|
214
|
-
else if (key === "version") out.version = v;
|
|
215
|
-
};
|
|
216
|
-
const setList = (key, items) => {
|
|
217
|
-
if (key === "stacks") out.stacks = items.filter(isDesignStack);
|
|
218
|
-
else out[key] = items;
|
|
219
|
-
};
|
|
220
|
-
for (const line of lines) {
|
|
221
|
-
const bulletMatch = /^\s*-\s+(.*)$/.exec(line);
|
|
222
|
-
if (listKey && bulletMatch) {
|
|
223
|
-
const parsed = parseList(bulletMatch[1] ?? "");
|
|
224
|
-
if (listKey === "stacks") out.stacks.push(...parsed.filter(isDesignStack));
|
|
225
|
-
else out[listKey].push(...parsed);
|
|
226
|
-
continue;
|
|
227
|
-
}
|
|
228
|
-
const kvMatch = /^([a-zA-Z_]+):\s*(.*)$/.exec(line);
|
|
229
|
-
if (!kvMatch) continue;
|
|
230
|
-
const key = kvMatch[1] ?? "";
|
|
231
|
-
const rest = (kvMatch[2] ?? "").trim();
|
|
232
|
-
if (key === "tags" || key === "stacks" || key === "themes") {
|
|
233
|
-
if (rest) {
|
|
234
|
-
setList(key, parseList(rest));
|
|
235
|
-
listKey = null;
|
|
236
|
-
} else {
|
|
237
|
-
listKey = key;
|
|
238
|
-
}
|
|
239
|
-
continue;
|
|
240
|
-
}
|
|
241
|
-
listKey = null;
|
|
242
|
-
setScalar(key, rest);
|
|
243
|
-
}
|
|
244
|
-
return out;
|
|
245
|
-
}
|
|
246
|
-
function narrowStackSections(body, stack) {
|
|
247
|
-
if (!stack) return body;
|
|
248
|
-
const stackHeading = /^##\s+Stack:\s*([a-z-]+)\s*$/gim;
|
|
249
|
-
const sections = [];
|
|
250
|
-
let match;
|
|
251
|
-
while ((match = stackHeading.exec(body)) !== null) {
|
|
252
|
-
sections.push({ id: (match[1] ?? "").trim(), start: match.index });
|
|
253
|
-
}
|
|
254
|
-
if (sections.length === 0) return body;
|
|
255
|
-
const preambleEnd = sections[0].start;
|
|
256
|
-
let result = body.slice(0, preambleEnd).trimEnd();
|
|
257
|
-
let sectionEnd;
|
|
258
|
-
for (let i = 0; i < sections.length; i++) {
|
|
259
|
-
const current = sections[i];
|
|
260
|
-
sectionEnd = i + 1 < sections.length ? sections[i + 1].start : body.length;
|
|
261
|
-
if (current.id === stack) {
|
|
262
|
-
result += `
|
|
263
|
-
|
|
264
|
-
${body.slice(current.start, sectionEnd).trimEnd()}`;
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
return `${result}
|
|
268
|
-
`;
|
|
269
|
-
}
|
|
270
|
-
var DefaultDesignKitLoader = class {
|
|
271
|
-
dirs;
|
|
272
|
-
cache;
|
|
273
|
-
bodyCache = /* @__PURE__ */ new Map();
|
|
274
|
-
tokenCache = /* @__PURE__ */ new Map();
|
|
275
|
-
rawTokenCache = /* @__PURE__ */ new Map();
|
|
276
|
-
constructor(opts) {
|
|
277
|
-
this.dirs = [
|
|
278
|
-
{ dir: opts.inProjectDir, source: "project" },
|
|
279
|
-
{ dir: opts.globalDir, source: "user" }
|
|
280
|
-
];
|
|
281
|
-
if (opts.bundledDir) this.dirs.push({ dir: opts.bundledDir, source: "bundled" });
|
|
282
|
-
}
|
|
283
|
-
async list() {
|
|
284
|
-
if (this.cache) return this.cache;
|
|
285
|
-
const found = [];
|
|
286
|
-
const seen = /* @__PURE__ */ new Set();
|
|
287
|
-
for (const { dir, source } of this.dirs) {
|
|
288
|
-
let entries;
|
|
289
|
-
try {
|
|
290
|
-
entries = await fs2.readdir(dir, { withFileTypes: true });
|
|
291
|
-
} catch {
|
|
292
|
-
continue;
|
|
293
|
-
}
|
|
294
|
-
for (const e of entries) {
|
|
295
|
-
if (!e.isDirectory()) continue;
|
|
296
|
-
const kitFile = path2.join(dir, e.name, KIT_FILE);
|
|
297
|
-
try {
|
|
298
|
-
const raw = await fs2.readFile(kitFile, "utf8");
|
|
299
|
-
const fm = parseKitFrontmatter(raw);
|
|
300
|
-
const id = fm.id ?? e.name;
|
|
301
|
-
if (!fm.name) continue;
|
|
302
|
-
if (seen.has(id)) continue;
|
|
303
|
-
seen.add(id);
|
|
304
|
-
found.push({
|
|
305
|
-
id,
|
|
306
|
-
name: fm.name,
|
|
307
|
-
aesthetic: fm.aesthetic ?? "",
|
|
308
|
-
tags: fm.tags,
|
|
309
|
-
stacks: fm.stacks.length > 0 ? fm.stacks : [...DESIGN_STACKS],
|
|
310
|
-
themes: fm.themes.length > 0 ? fm.themes : ["light", "dark"],
|
|
311
|
-
bestFor: fm.bestFor ?? "",
|
|
312
|
-
version: fm.version,
|
|
313
|
-
path: kitFile,
|
|
314
|
-
source
|
|
315
|
-
});
|
|
316
|
-
} catch {
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
this.cache = found;
|
|
321
|
-
return found;
|
|
322
|
-
}
|
|
323
|
-
/** Selectable kits only (excludes the reserved `_foundations` entry). */
|
|
324
|
-
async selectable() {
|
|
325
|
-
return (await this.list()).filter((k) => k.id !== FOUNDATIONS_ID);
|
|
326
|
-
}
|
|
327
|
-
async listEntries() {
|
|
328
|
-
return (await this.selectable()).map((k) => ({
|
|
329
|
-
id: k.id,
|
|
330
|
-
name: k.name,
|
|
331
|
-
aesthetic: k.aesthetic,
|
|
332
|
-
bestFor: k.bestFor,
|
|
333
|
-
stacks: k.stacks,
|
|
334
|
-
source: k.source
|
|
335
|
-
}));
|
|
336
|
-
}
|
|
337
|
-
async find(id) {
|
|
338
|
-
const all = await this.list();
|
|
339
|
-
const lower = id.toLowerCase();
|
|
340
|
-
return all.find((k) => k.id.toLowerCase() === lower);
|
|
341
|
-
}
|
|
342
|
-
async menuText() {
|
|
343
|
-
const entries = await this.listEntries();
|
|
344
|
-
if (entries.length === 0) return "";
|
|
345
|
-
const lines = ["## Design kits (pick ONE)"];
|
|
346
|
-
for (const e of entries) {
|
|
347
|
-
const stacks = e.stacks.join("/");
|
|
348
|
-
lines.push(`- **${e.id}** \u2014 ${e.aesthetic}`);
|
|
349
|
-
lines.push(` Best for: ${e.bestFor} \xB7 Stacks: ${stacks}`);
|
|
350
|
-
}
|
|
351
|
-
return lines.join("\n");
|
|
352
|
-
}
|
|
353
|
-
async readBody(id, stack) {
|
|
354
|
-
const key = `${id.toLowerCase()}:${stack ?? "*"}`;
|
|
355
|
-
const cached = this.bodyCache.get(key);
|
|
356
|
-
if (cached !== void 0) return cached;
|
|
357
|
-
const m = await this.find(id);
|
|
358
|
-
if (!m) throw new Error(`Design kit "${id}" not found`);
|
|
359
|
-
const raw = await fs2.readFile(m.path, "utf8");
|
|
360
|
-
const body = narrowStackSections(stripFrontmatter(raw), stack);
|
|
361
|
-
this.bodyCache.set(key, body);
|
|
362
|
-
return body;
|
|
363
|
-
}
|
|
364
|
-
/** Read a kit's OWN tokens.json (no foundations merge). Cached per id. */
|
|
365
|
-
async readRawTokens(id) {
|
|
366
|
-
const key = id.toLowerCase();
|
|
367
|
-
if (this.rawTokenCache.has(key)) return this.rawTokenCache.get(key);
|
|
368
|
-
const m = await this.find(id);
|
|
369
|
-
let tokens;
|
|
370
|
-
if (m) {
|
|
371
|
-
const tokensPath = path2.join(path2.dirname(m.path), TOKENS_FILE);
|
|
372
|
-
try {
|
|
373
|
-
const raw = await fs2.readFile(tokensPath, "utf8");
|
|
374
|
-
tokens = JSON.parse(raw);
|
|
375
|
-
} catch {
|
|
376
|
-
tokens = void 0;
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
this.rawTokenCache.set(key, tokens);
|
|
380
|
-
return tokens;
|
|
381
|
-
}
|
|
382
|
-
/**
|
|
383
|
-
* Read a kit's tokens with the `_foundations` base scales merged UNDER them
|
|
384
|
-
* (kit values win, per theme). This is why every kit resolves a full
|
|
385
|
-
* radius/space/type/motion scale even though its tokens.json only lists the
|
|
386
|
-
* axes it changes. `_foundations` itself is returned raw (no self-merge).
|
|
387
|
-
*/
|
|
388
|
-
async readTokens(id) {
|
|
389
|
-
const key = id.toLowerCase();
|
|
390
|
-
if (this.tokenCache.has(key)) return this.tokenCache.get(key);
|
|
391
|
-
let tokens;
|
|
392
|
-
if (key === FOUNDATIONS_ID) {
|
|
393
|
-
tokens = await this.readRawTokens(FOUNDATIONS_ID);
|
|
394
|
-
} else {
|
|
395
|
-
const own = await this.readRawTokens(id);
|
|
396
|
-
const base = await this.readRawTokens(FOUNDATIONS_ID);
|
|
397
|
-
tokens = mergeKitTokens(base, own);
|
|
398
|
-
}
|
|
399
|
-
this.tokenCache.set(key, tokens);
|
|
400
|
-
return tokens;
|
|
401
|
-
}
|
|
402
|
-
async foundationsText(stack) {
|
|
403
|
-
try {
|
|
404
|
-
return await this.readBody(FOUNDATIONS_ID, stack);
|
|
405
|
-
} catch {
|
|
406
|
-
return "";
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
invalidateCache() {
|
|
410
|
-
this.cache = void 0;
|
|
411
|
-
this.bodyCache.clear();
|
|
412
|
-
this.tokenCache.clear();
|
|
413
|
-
this.rawTokenCache.clear();
|
|
414
|
-
}
|
|
415
|
-
};
|
|
416
|
-
function mergeKitTokens(base, own) {
|
|
417
|
-
if (!base && !own) return void 0;
|
|
418
|
-
if (!base) return own;
|
|
419
|
-
if (!own) return base;
|
|
420
|
-
const mergeTheme = (b, o) => {
|
|
421
|
-
if (!b && !o) return void 0;
|
|
422
|
-
return { ...b ?? {}, ...o ?? {} };
|
|
423
|
-
};
|
|
424
|
-
const light = mergeTheme(base.light, own.light);
|
|
425
|
-
const dark = mergeTheme(base.dark, own.dark);
|
|
426
|
-
const out = {};
|
|
427
|
-
if (light) out.light = light;
|
|
428
|
-
if (dark) out.dark = dark;
|
|
429
|
-
return out;
|
|
430
|
-
}
|
|
431
|
-
function resolveBundledDesignKitsDir() {
|
|
432
|
-
try {
|
|
433
|
-
const here = path2.dirname(fileURLToPath(import.meta.url));
|
|
434
|
-
const candidates = [
|
|
435
|
-
path2.join(here, "design-kits"),
|
|
436
|
-
path2.join(here, "..", "design-kits"),
|
|
437
|
-
path2.join(here, "..", "..", "design-kits"),
|
|
438
|
-
path2.join(here, "..", "..", "..", "design-kits")
|
|
439
|
-
];
|
|
440
|
-
for (const c of candidates) {
|
|
441
|
-
if (existsSync(c)) return c;
|
|
442
|
-
}
|
|
443
|
-
} catch {
|
|
444
|
-
}
|
|
445
|
-
return void 0;
|
|
446
|
-
}
|
|
447
|
-
var loaderMemo = /* @__PURE__ */ new Map();
|
|
448
|
-
var LOADER_MEMO_MAX_PROJECTS = 32;
|
|
449
|
-
function getDesignKitLoader(projectRoot) {
|
|
450
|
-
const existing = loaderMemo.get(projectRoot);
|
|
451
|
-
if (existing) {
|
|
452
|
-
loaderMemo.delete(projectRoot);
|
|
453
|
-
loaderMemo.set(projectRoot, existing);
|
|
454
|
-
return existing;
|
|
455
|
-
}
|
|
456
|
-
const paths = resolveWstackPaths({ projectRoot });
|
|
457
|
-
const loader = new DefaultDesignKitLoader({
|
|
458
|
-
inProjectDir: paths.inProjectDesignKits,
|
|
459
|
-
globalDir: paths.globalDesignKits,
|
|
460
|
-
bundledDir: resolveBundledDesignKitsDir()
|
|
461
|
-
});
|
|
462
|
-
while (loaderMemo.size >= LOADER_MEMO_MAX_PROJECTS) {
|
|
463
|
-
const oldest = loaderMemo.keys().next().value;
|
|
464
|
-
if (oldest === void 0) break;
|
|
465
|
-
loaderMemo.delete(oldest);
|
|
466
|
-
}
|
|
467
|
-
loaderMemo.set(projectRoot, loader);
|
|
468
|
-
return loader;
|
|
469
|
-
}
|
|
470
|
-
function _resetDesignKitLoaderMemo() {
|
|
471
|
-
loaderMemo.clear();
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
// src/execution/design-project-store.ts
|
|
475
|
-
import { existsSync as existsSync2 } from "node:fs";
|
|
476
|
-
import * as fs3 from "node:fs/promises";
|
|
477
|
-
import * as path3 from "node:path";
|
|
478
|
-
var DESIGN_DIR = ".design";
|
|
479
|
-
function designProjectDir(projectRoot) {
|
|
480
|
-
return path3.join(projectRoot, DESIGN_DIR);
|
|
481
|
-
}
|
|
482
|
-
var RULE_FILES = ["rules.md", "RULES.md", "design.md"];
|
|
483
|
-
var rulesCache = /* @__PURE__ */ new Map();
|
|
484
|
-
var RULES_CACHE_MAX_PROJECTS = 32;
|
|
485
|
-
async function loadProjectDesignRules(projectRoot) {
|
|
486
|
-
if (rulesCache.has(projectRoot)) {
|
|
487
|
-
const cached = rulesCache.get(projectRoot);
|
|
488
|
-
rulesCache.delete(projectRoot);
|
|
489
|
-
rulesCache.set(projectRoot, cached);
|
|
490
|
-
return cached;
|
|
491
|
-
}
|
|
492
|
-
let rules;
|
|
493
|
-
for (const name of RULE_FILES) {
|
|
494
|
-
try {
|
|
495
|
-
const txt = await fs3.readFile(path3.join(designProjectDir(projectRoot), name), "utf8");
|
|
496
|
-
if (txt.trim()) {
|
|
497
|
-
rules = txt.trim();
|
|
498
|
-
break;
|
|
499
|
-
}
|
|
500
|
-
} catch {
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
while (rulesCache.size >= RULES_CACHE_MAX_PROJECTS) {
|
|
504
|
-
const oldest = rulesCache.keys().next().value;
|
|
505
|
-
if (oldest === void 0) break;
|
|
506
|
-
rulesCache.delete(oldest);
|
|
507
|
-
}
|
|
508
|
-
rulesCache.set(projectRoot, rules);
|
|
509
|
-
return rules;
|
|
510
|
-
}
|
|
511
|
-
function parseOverrides(value) {
|
|
512
|
-
if (!value || typeof value !== "object") return void 0;
|
|
513
|
-
const out = {};
|
|
514
|
-
for (const [k, v] of Object.entries(value)) {
|
|
515
|
-
if (typeof v === "string" && v.trim()) out[k] = v.trim();
|
|
516
|
-
}
|
|
517
|
-
return Object.keys(out).length > 0 ? out : void 0;
|
|
518
|
-
}
|
|
519
|
-
async function loadActiveKit(projectRoot) {
|
|
520
|
-
try {
|
|
521
|
-
const raw = await fs3.readFile(path3.join(designProjectDir(projectRoot), "active.json"), "utf8");
|
|
522
|
-
const parsed = JSON.parse(raw);
|
|
523
|
-
if (parsed && typeof parsed.kit === "string") {
|
|
524
|
-
return {
|
|
525
|
-
kit: parsed.kit,
|
|
526
|
-
stack: typeof parsed.stack === "string" ? parsed.stack : void 0,
|
|
527
|
-
overrides: parseOverrides(parsed.overrides)
|
|
528
|
-
};
|
|
529
|
-
}
|
|
530
|
-
} catch {
|
|
531
|
-
}
|
|
532
|
-
return void 0;
|
|
533
|
-
}
|
|
534
|
-
function applyTokenOverrides(tokens, overrides) {
|
|
535
|
-
if (!overrides || Object.keys(overrides).length === 0) return tokens;
|
|
536
|
-
const light = { ...tokens.light ?? {} };
|
|
537
|
-
const dark = { ...tokens.dark ?? {} };
|
|
538
|
-
for (const [key, val] of Object.entries(overrides)) {
|
|
539
|
-
const dot = key.indexOf(".");
|
|
540
|
-
const theme = dot > 0 ? key.slice(0, dot) : "";
|
|
541
|
-
if (theme === "light") {
|
|
542
|
-
light[key.slice(dot + 1)] = val;
|
|
543
|
-
} else if (theme === "dark") {
|
|
544
|
-
dark[key.slice(dot + 1)] = val;
|
|
545
|
-
} else {
|
|
546
|
-
light[key] = val;
|
|
547
|
-
dark[key] = val;
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
return { ...tokens, light, dark };
|
|
551
|
-
}
|
|
552
|
-
async function ensureDesignDir(projectRoot) {
|
|
553
|
-
const dir = designProjectDir(projectRoot);
|
|
554
|
-
await fs3.mkdir(dir, { recursive: true });
|
|
555
|
-
const gi = path3.join(dir, ".gitignore");
|
|
556
|
-
if (!existsSync2(gi)) {
|
|
557
|
-
try {
|
|
558
|
-
await fs3.writeFile(gi, "*\n");
|
|
559
|
-
} catch {
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
return dir;
|
|
563
|
-
}
|
|
564
|
-
async function recordKitChoice(projectRoot, kit, stack, source, isoTime, overrides) {
|
|
565
|
-
try {
|
|
566
|
-
const dir = await ensureDesignDir(projectRoot);
|
|
567
|
-
const record = { kit, stack: stack ?? null };
|
|
568
|
-
if (overrides && Object.keys(overrides).length > 0) record.overrides = overrides;
|
|
569
|
-
await fs3.writeFile(path3.join(dir, "active.json"), `${JSON.stringify(record, null, 2)}
|
|
570
|
-
`);
|
|
571
|
-
const line = `- ${isoTime} \xB7 kit=${kit}${stack ? ` stack=${stack}` : ""} \xB7 via=${source}
|
|
572
|
-
`;
|
|
573
|
-
await fs3.appendFile(path3.join(dir, "decisions.md"), line);
|
|
574
|
-
} catch {
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
async function recordOverrides(projectRoot, patch, isoTime) {
|
|
578
|
-
const active = await loadActiveKit(projectRoot);
|
|
579
|
-
if (!active) return void 0;
|
|
580
|
-
const merged = { ...active.overrides ?? {} };
|
|
581
|
-
for (const [k, v] of Object.entries(patch)) {
|
|
582
|
-
if (v?.trim()) merged[k] = v.trim();
|
|
583
|
-
else delete merged[k];
|
|
584
|
-
}
|
|
585
|
-
try {
|
|
586
|
-
const dir = await ensureDesignDir(projectRoot);
|
|
587
|
-
const record = { kit: active.kit, stack: active.stack ?? null };
|
|
588
|
-
if (Object.keys(merged).length > 0) record.overrides = merged;
|
|
589
|
-
await fs3.writeFile(path3.join(dir, "active.json"), `${JSON.stringify(record, null, 2)}
|
|
590
|
-
`);
|
|
591
|
-
const keys = Object.keys(patch).join(",");
|
|
592
|
-
await fs3.appendFile(
|
|
593
|
-
path3.join(dir, "decisions.md"),
|
|
594
|
-
`- ${isoTime} \xB7 kit=${active.kit} \xB7 override=${keys} \xB7 via=set
|
|
595
|
-
`
|
|
596
|
-
);
|
|
597
|
-
} catch {
|
|
598
|
-
}
|
|
599
|
-
return merged;
|
|
600
|
-
}
|
|
601
|
-
async function clearPersistedActiveKit(projectRoot) {
|
|
602
|
-
try {
|
|
603
|
-
await fs3.rm(path3.join(designProjectDir(projectRoot), "active.json"), { force: true });
|
|
604
|
-
} catch {
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
function _resetDesignRulesCache() {
|
|
608
|
-
rulesCache.clear();
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
// src/execution/design-color.ts
|
|
612
|
-
function clamp(n, lo, hi) {
|
|
613
|
-
return n < lo ? lo : n > hi ? hi : n;
|
|
614
|
-
}
|
|
615
|
-
function parseOklch(value) {
|
|
616
|
-
const m = /^oklch\(\s*([^)]+)\)$/i.exec(value.trim());
|
|
617
|
-
if (!m?.[1]) return null;
|
|
618
|
-
const [coords, alphaPart] = m[1].split("/");
|
|
619
|
-
const parts = (coords ?? "").trim().split(/\s+/);
|
|
620
|
-
if (parts.length < 3 || !parts[0] || !parts[1] || !parts[2]) return null;
|
|
621
|
-
const L = parseComponent(parts[0], true);
|
|
622
|
-
const C = parseComponent(parts[1], false);
|
|
623
|
-
const H = parseAngle(parts[2]);
|
|
624
|
-
if (L === null || C === null || H === null) return null;
|
|
625
|
-
let a = 1;
|
|
626
|
-
if (alphaPart !== void 0) {
|
|
627
|
-
const av = parseComponent(alphaPart.trim(), true);
|
|
628
|
-
if (av !== null) a = clamp(av, 0, 1);
|
|
629
|
-
}
|
|
630
|
-
return [clamp(L, 0, 1), Math.max(0, C), H, a];
|
|
631
|
-
}
|
|
632
|
-
function parseComponent(s, percentIsFraction) {
|
|
633
|
-
s = s.trim();
|
|
634
|
-
if (s.endsWith("%")) {
|
|
635
|
-
const n2 = Number.parseFloat(s.slice(0, -1));
|
|
636
|
-
if (!Number.isFinite(n2)) return null;
|
|
637
|
-
return percentIsFraction ? n2 / 100 : n2 / 100 * 0.4;
|
|
638
|
-
}
|
|
639
|
-
const n = Number.parseFloat(s);
|
|
640
|
-
return Number.isFinite(n) ? n : null;
|
|
641
|
-
}
|
|
642
|
-
function parseAngle(s) {
|
|
643
|
-
s = s.trim().replace(/deg$/i, "");
|
|
644
|
-
const n = Number.parseFloat(s);
|
|
645
|
-
return Number.isFinite(n) ? n : null;
|
|
646
|
-
}
|
|
647
|
-
function linearToSrgb(c) {
|
|
648
|
-
const v = c <= 31308e-7 ? 12.92 * c : 1.055 * c ** (1 / 2.4) - 0.055;
|
|
649
|
-
return clamp(v, 0, 1);
|
|
650
|
-
}
|
|
651
|
-
function toHex2(n) {
|
|
652
|
-
return Math.round(n * 255).toString(16).padStart(2, "0");
|
|
653
|
-
}
|
|
654
|
-
function oklchToHex(value) {
|
|
655
|
-
const parsed = parseOklch(value);
|
|
656
|
-
if (!parsed) return null;
|
|
657
|
-
const [L, C, Hdeg, alpha] = parsed;
|
|
658
|
-
const h = Hdeg * Math.PI / 180;
|
|
659
|
-
const a = C * Math.cos(h);
|
|
660
|
-
const b = C * Math.sin(h);
|
|
661
|
-
const l_ = L + 0.3963377774 * a + 0.2158037573 * b;
|
|
662
|
-
const m_ = L - 0.1055613458 * a - 0.0638541728 * b;
|
|
663
|
-
const s_ = L - 0.0894841775 * a - 1.291485548 * b;
|
|
664
|
-
const l = l_ * l_ * l_;
|
|
665
|
-
const m = m_ * m_ * m_;
|
|
666
|
-
const s = s_ * s_ * s_;
|
|
667
|
-
const r = 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s;
|
|
668
|
-
const g = -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s;
|
|
669
|
-
const bl = -0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s;
|
|
670
|
-
let hex = `#${toHex2(linearToSrgb(r))}${toHex2(linearToSrgb(g))}${toHex2(linearToSrgb(bl))}`;
|
|
671
|
-
if (alpha < 1) hex += toHex2(alpha);
|
|
672
|
-
return hex;
|
|
673
|
-
}
|
|
674
|
-
function colorToHex(value) {
|
|
675
|
-
const v = value.trim();
|
|
676
|
-
const oklch = oklchToHex(v);
|
|
677
|
-
if (oklch) return oklch.toLowerCase();
|
|
678
|
-
const hex = /^#([0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i.exec(v);
|
|
679
|
-
if (hex?.[1]) {
|
|
680
|
-
let h = hex[1].toLowerCase();
|
|
681
|
-
if (h.length === 3 || h.length === 4) {
|
|
682
|
-
h = h.split("").map((c) => c + c).join("");
|
|
683
|
-
}
|
|
684
|
-
return `#${h}`;
|
|
685
|
-
}
|
|
686
|
-
return null;
|
|
687
|
-
}
|
|
688
|
-
function isColorToken(value) {
|
|
689
|
-
return colorToHex(value) !== null;
|
|
690
|
-
}
|
|
691
|
-
var LENGTH_RE = /^-?\d*\.?\d+(?:px|rem|em|%|vw|vh|vmin|vmax|ch|pt)$/i;
|
|
692
|
-
var DURATION_RE = /^-?\d*\.?\d+(?:ms|s)$/i;
|
|
693
|
-
var NUMBER_RE = /^-?\d*\.?\d+$/;
|
|
694
|
-
var GRADIENT_RE = /\b(?:linear|radial|conic)-gradient\s*\(/i;
|
|
695
|
-
var OFFSET = String.raw`(?:0|-?\d*\.?\d+(?:px|rem|em))`;
|
|
696
|
-
var SHADOW_RE = new RegExp(String.raw`(?:^|,)\s*(?:inset\s+)?${OFFSET}\s+${OFFSET}`, "i");
|
|
697
|
-
function classifyTokenValue(value) {
|
|
698
|
-
const v = value.trim();
|
|
699
|
-
if (!v) return "raw";
|
|
700
|
-
if (isColorToken(v)) return "color";
|
|
701
|
-
if (GRADIENT_RE.test(v)) return "gradient";
|
|
702
|
-
if (LENGTH_RE.test(v)) return "length";
|
|
703
|
-
if (DURATION_RE.test(v)) return "duration";
|
|
704
|
-
if (NUMBER_RE.test(v)) return "number";
|
|
705
|
-
if (SHADOW_RE.test(v)) return "shadow";
|
|
706
|
-
const GENERIC_FONTS = /\b(sans-serif|serif|monospace|system-ui|ui-sans-serif|ui-monospace|ui-serif|cursive|fantasy|emoji)\b/i;
|
|
707
|
-
if (v.includes(",") || GENERIC_FONTS.test(v) || /^["']?[A-Za-z][\w '-]*["']?$/.test(v)) {
|
|
708
|
-
return "font";
|
|
709
|
-
}
|
|
710
|
-
return "raw";
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
// src/execution/design-verify.ts
|
|
714
|
-
var HEX_RE = /#[0-9a-fA-F]{3,8}\b/g;
|
|
715
|
-
var FUNC_COLOR_RE = /\b(?:oklch|rgb|rgba|hsl|hsla)\([^)]*\)/gi;
|
|
716
|
-
var ARBITRARY_RADIUS_RE = /\brounded(?:-[a-z]+)?-\[[^\]]+\]/g;
|
|
717
|
-
var ARBITRARY_SPACING_RE = /\b(?:p|px|py|pt|pr|pb|pl|m|mx|my|mt|mr|mb|ml|gap(?:-[xy])?|space-[xy])-\[[^\]]+\]/g;
|
|
718
|
-
var RAW_RADIUS_RE = /border-radius:\s*([^;{}]+)/gi;
|
|
719
|
-
var TW_GENERIC_RE = /\b(?:bg|text|border|ring|from|to|via|fill|stroke|decoration|outline|shadow|accent|caret|divide)-(?:slate|gray|grey|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-(?:50|100|200|300|400|500|600|700|800|900|950)\b/g;
|
|
720
|
-
function kebab(name) {
|
|
721
|
-
return name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/_/g, "-").toLowerCase();
|
|
722
|
-
}
|
|
723
|
-
function buildPalette(tokens) {
|
|
724
|
-
const hexes = /* @__PURE__ */ new Set();
|
|
725
|
-
const names = /* @__PURE__ */ new Set();
|
|
726
|
-
for (const set of [tokens.light, tokens.dark]) {
|
|
727
|
-
if (!set) continue;
|
|
728
|
-
for (const [k, v] of Object.entries(set)) {
|
|
729
|
-
if (!isColorToken(v)) continue;
|
|
730
|
-
names.add(k);
|
|
731
|
-
const hex = colorToHex(v);
|
|
732
|
-
if (hex) hexes.add(hex.toLowerCase().slice(0, 7));
|
|
733
|
-
}
|
|
734
|
-
}
|
|
735
|
-
return { hexes, names: [...names] };
|
|
736
|
-
}
|
|
737
|
-
function verifyFiles(tokens, files) {
|
|
738
|
-
const { hexes, names } = buildPalette(tokens);
|
|
739
|
-
const tokenNamePatterns = names.map((n) => kebab(n));
|
|
740
|
-
const violations = [];
|
|
741
|
-
let onPalette = 0;
|
|
742
|
-
let offPalette = 0;
|
|
743
|
-
for (const { path: path4, text } of files) {
|
|
744
|
-
const lines = text.split("\n");
|
|
745
|
-
lines.forEach((lineText, i) => {
|
|
746
|
-
const lineNo = i + 1;
|
|
747
|
-
const flag = (snippet, reason, axis = "color") => {
|
|
748
|
-
violations.push({ file: path4, line: lineNo, snippet: snippet.slice(0, 80), reason, axis });
|
|
749
|
-
};
|
|
750
|
-
for (const re of [HEX_RE, FUNC_COLOR_RE]) {
|
|
751
|
-
re.lastIndex = 0;
|
|
752
|
-
for (const m of lineText.matchAll(re)) {
|
|
753
|
-
const lit = m[0];
|
|
754
|
-
const hex = colorToHex(lit);
|
|
755
|
-
if (hex && hexes.has(hex.toLowerCase().slice(0, 7))) {
|
|
756
|
-
onPalette++;
|
|
757
|
-
} else if (hex) {
|
|
758
|
-
offPalette++;
|
|
759
|
-
flag(lit, "off-palette hardcoded color (not a kit token)");
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
TW_GENERIC_RE.lastIndex = 0;
|
|
764
|
-
for (const m of lineText.matchAll(TW_GENERIC_RE)) {
|
|
765
|
-
offPalette++;
|
|
766
|
-
flag(m[0], "generic Tailwind palette utility \u2014 use kit token colors");
|
|
767
|
-
}
|
|
768
|
-
ARBITRARY_RADIUS_RE.lastIndex = 0;
|
|
769
|
-
for (const m of lineText.matchAll(ARBITRARY_RADIUS_RE)) {
|
|
770
|
-
flag(m[0], "arbitrary radius \u2014 use a kit radius scale token (rounded-sm\u2026rounded-full)", "radius");
|
|
771
|
-
}
|
|
772
|
-
ARBITRARY_SPACING_RE.lastIndex = 0;
|
|
773
|
-
for (const m of lineText.matchAll(ARBITRARY_SPACING_RE)) {
|
|
774
|
-
flag(m[0], "arbitrary spacing \u2014 use a kit spacing scale token (p-1\u2026p-12)", "spacing");
|
|
775
|
-
}
|
|
776
|
-
RAW_RADIUS_RE.lastIndex = 0;
|
|
777
|
-
for (const m of lineText.matchAll(RAW_RADIUS_RE)) {
|
|
778
|
-
const val = (m[1] ?? "").trim();
|
|
779
|
-
if (!val || val.startsWith("var(")) continue;
|
|
780
|
-
if (/^(0|0px|9999px|50%|inherit|initial|unset|full)$/i.test(val)) continue;
|
|
781
|
-
flag(`border-radius: ${val}`, "hardcoded radius \u2014 use var(--radius-*) / a kit radius token", "radius");
|
|
782
|
-
}
|
|
783
|
-
for (const tn of tokenNamePatterns) {
|
|
784
|
-
if (tn.length < 2) continue;
|
|
785
|
-
if (lineText.includes(`--${tn}`) || new RegExp(`\\b(?:bg|text|border|ring|fill|stroke)-${tn}\\b`).test(lineText)) {
|
|
786
|
-
onPalette++;
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
});
|
|
790
|
-
}
|
|
791
|
-
const total = onPalette + offPalette;
|
|
792
|
-
const score = total === 0 ? 1 : onPalette / total;
|
|
793
|
-
return {
|
|
794
|
-
filesScanned: files.length,
|
|
795
|
-
palette: [...hexes],
|
|
796
|
-
tokenNames: names,
|
|
797
|
-
violations,
|
|
798
|
-
score,
|
|
799
|
-
ok: violations.length === 0
|
|
800
|
-
};
|
|
801
|
-
}
|
|
802
|
-
var UI_EXT_RE = /\.(css|scss|sass|less|tsx|jsx|vue|svelte|astro|html?)$/i;
|
|
803
|
-
var SKIP_DIR = /* @__PURE__ */ new Set([
|
|
804
|
-
"node_modules",
|
|
805
|
-
".git",
|
|
806
|
-
"dist",
|
|
807
|
-
"build",
|
|
808
|
-
".next",
|
|
809
|
-
"out",
|
|
810
|
-
"coverage",
|
|
811
|
-
".wrongstack",
|
|
812
|
-
".design"
|
|
813
|
-
]);
|
|
814
|
-
async function walkUiFiles(root, max = 200) {
|
|
815
|
-
const { default: fs4 } = await import("node:fs/promises");
|
|
816
|
-
const { default: nodePath } = await import("node:path");
|
|
817
|
-
const found = [];
|
|
818
|
-
async function rec(dir, depth) {
|
|
819
|
-
if (found.length >= max || depth > 8) return;
|
|
820
|
-
let entries;
|
|
821
|
-
try {
|
|
822
|
-
entries = await fs4.readdir(dir, { withFileTypes: true });
|
|
823
|
-
} catch {
|
|
824
|
-
return;
|
|
825
|
-
}
|
|
826
|
-
for (const e of entries) {
|
|
827
|
-
if (found.length >= max) return;
|
|
828
|
-
if (e.isDirectory()) {
|
|
829
|
-
if (SKIP_DIR.has(e.name) || e.name.startsWith(".")) continue;
|
|
830
|
-
await rec(nodePath.join(dir, e.name), depth + 1);
|
|
831
|
-
} else if (UI_EXT_RE.test(e.name)) {
|
|
832
|
-
found.push(nodePath.join(dir, e.name));
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
}
|
|
836
|
-
await rec(root, 0);
|
|
837
|
-
return found;
|
|
838
|
-
}
|
|
839
|
-
async function runDesignVerify(projectRoot, tokens, explicitFiles) {
|
|
840
|
-
const { default: fs4 } = await import("node:fs/promises");
|
|
841
|
-
const { default: nodePath } = await import("node:path");
|
|
842
|
-
const abs = explicitFiles && explicitFiles.length > 0 ? explicitFiles.map((f) => nodePath.isAbsolute(f) ? f : nodePath.join(projectRoot, f)) : await walkUiFiles(projectRoot);
|
|
843
|
-
const files = [];
|
|
844
|
-
for (const a of abs) {
|
|
845
|
-
try {
|
|
846
|
-
files.push({ path: nodePath.relative(projectRoot, a), text: await fs4.readFile(a, "utf8") });
|
|
847
|
-
} catch {
|
|
848
|
-
}
|
|
849
|
-
}
|
|
850
|
-
return verifyFiles(tokens, files);
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
// src/execution/design-detect.ts
|
|
854
|
-
var META_KEY = "designStudio";
|
|
855
|
-
function getDesignState(ctx) {
|
|
856
|
-
const v = ctx.meta[META_KEY];
|
|
857
|
-
return v && typeof v === "object" ? v : void 0;
|
|
858
|
-
}
|
|
859
|
-
function ensureState(ctx) {
|
|
860
|
-
let s = getDesignState(ctx);
|
|
861
|
-
if (!s) {
|
|
862
|
-
s = { active: false, signals: [] };
|
|
863
|
-
ctx.meta[META_KEY] = s;
|
|
864
|
-
}
|
|
865
|
-
return s;
|
|
866
|
-
}
|
|
867
|
-
function activateDesign(ctx, signals, stack) {
|
|
868
|
-
const s = ensureState(ctx);
|
|
869
|
-
s.active = true;
|
|
870
|
-
for (const sig of signals) if (!s.signals.includes(sig)) s.signals.push(sig);
|
|
871
|
-
if (stack && !s.stack) s.stack = stack;
|
|
872
|
-
return s;
|
|
873
|
-
}
|
|
874
|
-
function setActiveKit(ctx, kitId, stack, overrides) {
|
|
875
|
-
const s = ensureState(ctx);
|
|
876
|
-
s.active = true;
|
|
877
|
-
s.activeKit = kitId;
|
|
878
|
-
if (stack) s.stack = stack;
|
|
879
|
-
if (overrides !== void 0) s.overrides = overrides;
|
|
880
|
-
}
|
|
881
|
-
function setDesignOverrides(ctx, overrides) {
|
|
882
|
-
const s = ensureState(ctx);
|
|
883
|
-
s.overrides = { ...s.overrides ?? {}, ...overrides };
|
|
884
|
-
}
|
|
885
|
-
function clearActiveKit(ctx) {
|
|
886
|
-
const s = getDesignState(ctx);
|
|
887
|
-
if (s) s.activeKit = void 0;
|
|
888
|
-
}
|
|
889
|
-
var STACK_HINTS = [
|
|
890
|
-
{ re: /\b(flutter|dart)\b/i, stack: "flutter", label: "flutter" },
|
|
891
|
-
{ re: /\bjetpack\s*compose\b|\bcompose\b/i, stack: "compose", label: "compose" },
|
|
892
|
-
{ re: /\bswift\s*ui\b|\bswiftui\b/i, stack: "swiftui", label: "swiftui" },
|
|
893
|
-
{ re: /\b(react\s*native|expo|nativewind)\b/i, stack: "react-native", label: "react-native" }
|
|
894
|
-
];
|
|
895
|
-
var WEB_INTENT_RE = /\b(ui|ux|frontend|front-end|landing\s*page|web\s*site|website|web\s*app|webapp|dashboard|screen|design\s*system|component|react|next\.?js|vue|svelte|tailwind|shadcn|css|theme|redesign|style|interface|hero\s*section|navbar|sidebar|button|modal|form\s*design)\b/i;
|
|
896
|
-
var GENERIC_UI_RE = /\b(ui|interface|screen|page|app|component|design)\b/i;
|
|
897
|
-
function detectFrontendIntent(text) {
|
|
898
|
-
if (!text) return null;
|
|
899
|
-
const signals = [];
|
|
900
|
-
let stack;
|
|
901
|
-
for (const h of STACK_HINTS) {
|
|
902
|
-
if (h.re.test(text)) {
|
|
903
|
-
signals.push(`intent:${h.label}`);
|
|
904
|
-
stack ??= h.stack;
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
const webMatch = WEB_INTENT_RE.exec(text);
|
|
908
|
-
if (webMatch) {
|
|
909
|
-
signals.push(`intent:${webMatch[0].toLowerCase().replace(/\s+/g, "-")}`);
|
|
910
|
-
stack ??= "web";
|
|
911
|
-
} else if (stack === void 0 && GENERIC_UI_RE.test(text) && /\b(build|make|create|design|implement|add)\b/i.test(text)) {
|
|
912
|
-
signals.push("intent:ui");
|
|
913
|
-
stack = "web";
|
|
914
|
-
}
|
|
915
|
-
if (signals.length === 0) return null;
|
|
916
|
-
return stack ? { stack, signals } : { signals };
|
|
917
|
-
}
|
|
918
|
-
var FRONTEND_EXT_STACK = [
|
|
919
|
-
{ re: /\.(tsx|jsx)$/i, stack: "web" },
|
|
920
|
-
{ re: /\.(css|scss|sass|less)$/i, stack: "web" },
|
|
921
|
-
{ re: /\.(vue|svelte|astro)$/i, stack: "web" },
|
|
922
|
-
{ re: /\.html?$/i, stack: "web" },
|
|
923
|
-
{ re: /\.dart$/i, stack: "flutter" },
|
|
924
|
-
{ re: /\.swift$/i, stack: "swiftui" }
|
|
925
|
-
];
|
|
926
|
-
function detectFrontendFile(filePath) {
|
|
927
|
-
if (!filePath) return null;
|
|
928
|
-
for (const { re, stack } of FRONTEND_EXT_STACK) {
|
|
929
|
-
if (re.test(filePath)) return stack ? { stack } : {};
|
|
930
|
-
}
|
|
931
|
-
return null;
|
|
932
|
-
}
|
|
933
|
-
function makeDesignDetectUserInputMiddleware() {
|
|
934
|
-
return {
|
|
935
|
-
name: "DesignStudioDetectIntent",
|
|
936
|
-
owner: "core",
|
|
937
|
-
async handler(payload, next) {
|
|
938
|
-
const hit = detectFrontendIntent(payload.text);
|
|
939
|
-
if (hit) activateDesign(payload.ctx, hit.signals, hit.stack);
|
|
940
|
-
return next(payload);
|
|
941
|
-
}
|
|
942
|
-
};
|
|
943
|
-
}
|
|
944
|
-
function makeDesignDetectToolCallMiddleware() {
|
|
945
|
-
return {
|
|
946
|
-
name: "DesignStudioDetectFile",
|
|
947
|
-
owner: "core",
|
|
948
|
-
async handler(payload, next) {
|
|
949
|
-
const name = payload.toolUse?.name;
|
|
950
|
-
if (name === "write" || name === "edit" || name === "replace" || name === "patch") {
|
|
951
|
-
const input = payload.toolUse.input;
|
|
952
|
-
const p = typeof input?.path === "string" ? input.path : "";
|
|
953
|
-
const hit = detectFrontendFile(p);
|
|
954
|
-
if (hit) activateDesign(payload.ctx, [`file:${p}`], hit.stack);
|
|
955
|
-
}
|
|
956
|
-
return next(payload);
|
|
957
|
-
}
|
|
958
|
-
};
|
|
959
|
-
}
|
|
960
|
-
var WRITE_TOOLS = /* @__PURE__ */ new Set(["write", "edit", "replace", "patch"]);
|
|
961
|
-
function makeDesignVerifyToolCallMiddleware() {
|
|
962
|
-
return {
|
|
963
|
-
name: "DesignStudioVerifyWrite",
|
|
964
|
-
owner: "core",
|
|
965
|
-
async handler(payload, next) {
|
|
966
|
-
const out = await next(payload);
|
|
967
|
-
try {
|
|
968
|
-
const name = out.toolUse?.name;
|
|
969
|
-
if (!name || !WRITE_TOOLS.has(name)) return out;
|
|
970
|
-
if (out.result?.is_error) return out;
|
|
971
|
-
const state = getDesignState(out.ctx);
|
|
972
|
-
if (!state?.activeKit) return out;
|
|
973
|
-
const input = out.toolUse.input;
|
|
974
|
-
const p = typeof input?.path === "string" ? input.path : "";
|
|
975
|
-
if (!p || !detectFrontendFile(p)) return out;
|
|
976
|
-
const ctx = out.ctx;
|
|
977
|
-
const { default: fs4 } = await import("node:fs/promises");
|
|
978
|
-
const { default: nodePath } = await import("node:path");
|
|
979
|
-
const abs = nodePath.isAbsolute(p) ? p : nodePath.join(ctx.projectRoot, p);
|
|
980
|
-
const text = await fs4.readFile(abs, "utf8").catch(() => "");
|
|
981
|
-
if (!text) return out;
|
|
982
|
-
const loader = getDesignKitLoader(ctx.projectRoot);
|
|
983
|
-
const rawTokens = await loader.readTokens(state.activeKit);
|
|
984
|
-
if (!rawTokens) return out;
|
|
985
|
-
const persisted = await loadActiveKit(ctx.projectRoot).catch(() => void 0);
|
|
986
|
-
const tokens = applyTokenOverrides(rawTokens, persisted?.overrides ?? state.overrides);
|
|
987
|
-
const rel = nodePath.relative(ctx.projectRoot, abs);
|
|
988
|
-
const report = verifyFiles(tokens, [{ path: rel, text }]);
|
|
989
|
-
if (report.violations.length === 0) return out;
|
|
990
|
-
const top = report.violations.slice(0, 5).map((v) => ` L${v.line}: [${v.axis ?? "color"}] ${v.snippet} \u2014 ${v.reason}`).join("\n");
|
|
991
|
-
const more = report.violations.length > 5 ? `
|
|
992
|
-
\u2026and ${report.violations.length - 5} more` : "";
|
|
993
|
-
const axes = [...new Set(report.violations.map((v) => v.axis ?? "color"))].join(", ");
|
|
994
|
-
out.result.content += `
|
|
995
|
-
|
|
996
|
-
\u26A0\uFE0F Design Studio (kit "${state.activeKit}"): ${report.violations.length} design-token issue(s) [${axes}] in ${rel}. Use the kit's scale tokens (or the materialized CSS vars / utilities) instead:
|
|
997
|
-
${top}${more}`;
|
|
998
|
-
} catch {
|
|
999
|
-
}
|
|
1000
|
-
return out;
|
|
1001
|
-
}
|
|
1002
|
-
};
|
|
1003
|
-
}
|
|
1004
|
-
var BASELINE = [
|
|
1005
|
-
"**Non-negotiable baseline (every UI you write):**",
|
|
1006
|
-
"- Mobile-first & fully responsive; respect safe-area insets on native.",
|
|
1007
|
-
"- Ship BOTH light and dark themes from one token set (no hard-coded colors).",
|
|
1008
|
-
"- WCAG 2.2 AA: semantic markup, focus-visible, 4.5:1 contrast, labelled controls, hit targets \u226544px.",
|
|
1009
|
-
"- Tasteful motion with `prefers-reduced-motion` honored.",
|
|
1010
|
-
"- Use current stack defaults (e.g. web: React 19 + Tailwind v4 `@theme`/OKLCH + shadcn/ui + Motion)."
|
|
1011
|
-
].join("\n");
|
|
1012
|
-
function makeDesignStudioRequestMiddleware(deps) {
|
|
1013
|
-
const { ctx, loader } = deps;
|
|
1014
|
-
return {
|
|
1015
|
-
name: "DesignStudioInject",
|
|
1016
|
-
owner: "core",
|
|
1017
|
-
async handler(req, next) {
|
|
1018
|
-
if (deps.enabled && !deps.enabled()) return next(req);
|
|
1019
|
-
const state = getDesignState(ctx);
|
|
1020
|
-
if (!state?.active) return next(req);
|
|
1021
|
-
let text;
|
|
1022
|
-
if (state.activeKit) {
|
|
1023
|
-
const ov = state.overrides && Object.keys(state.overrides).length > 0 ? `
|
|
1024
|
-
User color overrides (these WIN over kit tokens): ${Object.entries(state.overrides).map(([k, v]) => `${k}=${v}`).join(", ")}.` : "";
|
|
1025
|
-
text = `## Active design kit: ${state.activeKit}
|
|
1026
|
-
Adhere strictly to its tokens, components, and patterns. Keep light/dark + responsive + WCAG AA. Re-load the full spec with \`design use ${state.activeKit}\` if unsure.${ov}`;
|
|
1027
|
-
} else {
|
|
1028
|
-
const menu = await loader.menuText().catch(() => "");
|
|
1029
|
-
const stackLine = state.stack ? ` (detected stack: ${state.stack})` : "";
|
|
1030
|
-
const parts = [
|
|
1031
|
-
`## Design Studio \u2014 UI work detected${stackLine}`,
|
|
1032
|
-
"Before writing UI code, COMMIT to ONE coherent design direction. Do NOT produce generic, default-framework, unstyled output.",
|
|
1033
|
-
"",
|
|
1034
|
-
menu || "(no kits installed)",
|
|
1035
|
-
"",
|
|
1036
|
-
BASELINE,
|
|
1037
|
-
"",
|
|
1038
|
-
"Next: call `design list` to review, then `design use <kit-id> --stack <stack>` to load the full spec, then implement it faithfully. A user can also pin one with `/design <kit-id>`."
|
|
1039
|
-
];
|
|
1040
|
-
text = parts.join("\n");
|
|
1041
|
-
}
|
|
1042
|
-
const rules = await loadProjectDesignRules(ctx.projectRoot).catch(() => void 0);
|
|
1043
|
-
if (rules) {
|
|
1044
|
-
text += `
|
|
1045
|
-
|
|
1046
|
-
## Project design rules (.design/rules.md) \u2014 these OVERRIDE kit defaults on conflict
|
|
1047
|
-
${rules}`;
|
|
1048
|
-
}
|
|
1049
|
-
const block = {
|
|
1050
|
-
type: "text",
|
|
1051
|
-
text,
|
|
1052
|
-
cache_control: { type: "ephemeral" }
|
|
1053
|
-
};
|
|
1054
|
-
const system = Array.isArray(req.system) ? [...req.system, block] : [block];
|
|
1055
|
-
return next({ ...req, system });
|
|
1056
|
-
}
|
|
1057
|
-
};
|
|
1058
|
-
}
|
|
1059
|
-
function installDesignStudioMiddleware(deps) {
|
|
1060
|
-
if (deps.enabled === false) return;
|
|
1061
|
-
const loader = getDesignKitLoader(deps.ctx.projectRoot);
|
|
1062
|
-
deps.pipelines.userInput.prepend(makeDesignDetectUserInputMiddleware());
|
|
1063
|
-
deps.pipelines.toolCall.prepend(makeDesignDetectToolCallMiddleware());
|
|
1064
|
-
deps.pipelines.toolCall.prepend(makeDesignVerifyToolCallMiddleware());
|
|
1065
|
-
deps.pipelines.request.prepend(makeDesignStudioRequestMiddleware({ ctx: deps.ctx, loader }));
|
|
1066
|
-
void loadActiveKit(deps.ctx.projectRoot).then((persisted) => {
|
|
1067
|
-
if (persisted && !getDesignState(deps.ctx)?.activeKit) {
|
|
1068
|
-
const stack = persisted.stack && isDesignStack(persisted.stack) ? persisted.stack : void 0;
|
|
1069
|
-
setActiveKit(deps.ctx, persisted.kit, stack, persisted.overrides);
|
|
1070
|
-
}
|
|
1071
|
-
}).catch(() => {
|
|
1072
|
-
});
|
|
1073
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
DefaultDesignKitLoader,
|
|
3
|
+
_resetDesignKitLoaderMemo,
|
|
4
|
+
_resetDesignRulesCache,
|
|
5
|
+
activateDesign,
|
|
6
|
+
applyTokenOverrides,
|
|
7
|
+
classifyTokenValue,
|
|
8
|
+
clearActiveKit,
|
|
9
|
+
clearPersistedActiveKit,
|
|
10
|
+
colorToHex,
|
|
11
|
+
designProjectDir,
|
|
12
|
+
detectFrontendFile,
|
|
13
|
+
detectFrontendIntent,
|
|
14
|
+
getDesignKitLoader,
|
|
15
|
+
getDesignState,
|
|
16
|
+
installDesignStudioMiddleware,
|
|
17
|
+
isColorToken,
|
|
18
|
+
loadActiveKit,
|
|
19
|
+
loadProjectDesignRules,
|
|
20
|
+
makeDesignDetectToolCallMiddleware,
|
|
21
|
+
makeDesignDetectUserInputMiddleware,
|
|
22
|
+
makeDesignStudioRequestMiddleware,
|
|
23
|
+
makeDesignVerifyToolCallMiddleware,
|
|
24
|
+
recordKitChoice,
|
|
25
|
+
recordOverrides,
|
|
26
|
+
resolveBundledDesignKitsDir,
|
|
27
|
+
runDesignVerify,
|
|
28
|
+
setActiveKit,
|
|
29
|
+
setDesignOverrides,
|
|
30
|
+
verifyFiles
|
|
31
|
+
} from "../chunk-5DSMEVA4.js";
|
|
32
|
+
import {
|
|
33
|
+
isDesignStack
|
|
34
|
+
} from "../chunk-G5S3S5ZN.js";
|
|
35
|
+
import "../chunk-WEBMSQP2.js";
|
|
36
|
+
import "../chunk-YNCVSFGF.js";
|
|
1074
37
|
|
|
1075
38
|
// src/execution/design-materialize.ts
|
|
1076
39
|
var SCALE_PREFIXES = [
|
|
@@ -1134,7 +97,7 @@ function splitColors(set) {
|
|
|
1134
97
|
}
|
|
1135
98
|
return { colors, others };
|
|
1136
99
|
}
|
|
1137
|
-
function
|
|
100
|
+
function kebab(name) {
|
|
1138
101
|
return name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/_/g, "-").toLowerCase();
|
|
1139
102
|
}
|
|
1140
103
|
function pascal(name) {
|
|
@@ -1160,9 +123,9 @@ function hexToArgb(hex) {
|
|
|
1160
123
|
function genWebCss(tokens, kitId) {
|
|
1161
124
|
const light = tokens.light ?? {};
|
|
1162
125
|
const dark = tokens.dark ?? {};
|
|
1163
|
-
const rawVars = (set) => Object.entries(set).filter(([k]) => !isScaleToken(k)).map(([k, v]) => ` --${
|
|
126
|
+
const rawVars = (set) => Object.entries(set).filter(([k]) => !isScaleToken(k)).map(([k, v]) => ` --${kebab(k)}: ${v};`).join("\n");
|
|
1164
127
|
const colorKeys = Object.keys(light).filter((k) => !isScaleToken(k) && isColorToken(light[k] ?? ""));
|
|
1165
|
-
const colorMap = colorKeys.map((k) => ` --color-${
|
|
128
|
+
const colorMap = colorKeys.map((k) => ` --color-${kebab(k)}: var(--${kebab(k)});`).join("\n");
|
|
1166
129
|
const scaleKeys = Object.keys(light).filter(isScaleToken);
|
|
1167
130
|
const scaleTheme = scaleKeys.map((k) => ` ${themeVar(k)}: ${light[k]};`).join("\n");
|
|
1168
131
|
const scaleDark = scaleKeys.filter((k) => dark[k] !== void 0 && dark[k] !== light[k]).map((k) => ` ${themeVar(k)}: ${dark[k]};`).join("\n");
|
|
@@ -1316,20 +279,20 @@ function materializeTokens(opts) {
|
|
|
1316
279
|
return { path: opts.outPath?.trim() || DEFAULT_PATHS[stack], content, format };
|
|
1317
280
|
}
|
|
1318
281
|
async function resolveMaterializeTarget(resultPath, projectRoot) {
|
|
1319
|
-
const
|
|
1320
|
-
const
|
|
282
|
+
const fs = await import("node:fs/promises");
|
|
283
|
+
const path = await import("node:path");
|
|
1321
284
|
const resolveReal = async (p) => {
|
|
1322
|
-
const resolved =
|
|
285
|
+
const resolved = path.resolve(p);
|
|
1323
286
|
let probe = resolved;
|
|
1324
287
|
const missing = [];
|
|
1325
288
|
for (; ; ) {
|
|
1326
289
|
try {
|
|
1327
|
-
return
|
|
290
|
+
return path.resolve(await fs.realpath(probe), ...missing);
|
|
1328
291
|
} catch (err) {
|
|
1329
292
|
if (err.code === "ENOENT") {
|
|
1330
|
-
const parent =
|
|
293
|
+
const parent = path.dirname(probe);
|
|
1331
294
|
if (parent === probe) return resolved;
|
|
1332
|
-
missing.unshift(
|
|
295
|
+
missing.unshift(path.basename(probe));
|
|
1333
296
|
probe = parent;
|
|
1334
297
|
continue;
|
|
1335
298
|
}
|
|
@@ -1338,11 +301,11 @@ async function resolveMaterializeTarget(resultPath, projectRoot) {
|
|
|
1338
301
|
}
|
|
1339
302
|
};
|
|
1340
303
|
const root = await resolveReal(projectRoot);
|
|
1341
|
-
const absResolved =
|
|
1342
|
-
const absParent = await resolveReal(
|
|
1343
|
-
const abs =
|
|
1344
|
-
const rel =
|
|
1345
|
-
if (rel.startsWith("..") ||
|
|
304
|
+
const absResolved = path.isAbsolute(resultPath) ? path.resolve(resultPath) : path.resolve(path.join(projectRoot, resultPath));
|
|
305
|
+
const absParent = await resolveReal(path.dirname(absResolved));
|
|
306
|
+
const abs = path.join(absParent, path.basename(absResolved));
|
|
307
|
+
const rel = path.relative(root, abs);
|
|
308
|
+
if (rel.startsWith("..") || path.isAbsolute(rel)) {
|
|
1346
309
|
throw new Error(`design: materialize path "${resultPath}" would escape the project root`);
|
|
1347
310
|
}
|
|
1348
311
|
return abs;
|