@reqvire-org/ads 0.3.1 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bootstrap-ds/ADS.md +29 -0
- package/dist/bootstrap-ds/design-system/tokens/base.css +4 -1
- package/dist/bootstrap-ds/design-system/tokens/colors.css +2 -2
- package/dist/check.mjs +27 -4
- package/dist/contracts/foundation.json +211 -0
- package/dist/foundation.mjs +114 -0
- package/dist/mcp/build-status.mjs +22 -9
- package/dist/mcp/preview-watch.mjs +35 -12
- package/dist/scenarios-compiler.mjs +86 -43
- package/dist/skills/ads-assimilate/SKILL.md +19 -0
- package/dist/skills/ads-assimilate/references/cli.md +3 -2
- package/dist/skills/ads-break-ui/SKILL.md +19 -0
- package/dist/skills/ads-designer/SKILL.md +19 -0
- package/dist/skills/ads-designer/assets/bootstrap-ds/ADS.md +29 -0
- package/dist/skills/ads-designer/assets/bootstrap-ds/design-system/tokens/base.css +4 -1
- package/dist/skills/ads-designer/assets/bootstrap-ds/design-system/tokens/colors.css +2 -2
- package/dist/skills/ads-designer/references/cli.md +3 -2
- package/dist/skills/ads-import/SKILL.md +19 -0
- package/dist/skills/ads-import/references/cli.md +3 -2
- package/dist/skills/ads-motion/SKILL.md +19 -0
- package/docs/ADS.md +29 -0
- package/docs/ARCHITECTURE.md +32 -3
- package/docs/CHANGELOG.md +16 -0
- package/docs/GUIDE.md +9 -1
- package/docs/MCP.md +18 -7
- package/package.json +2 -1
|
@@ -2,7 +2,7 @@ import { spawn } from "node:child_process";
|
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { statSync, mkdirSync, writeFileSync, renameSync, rmSync, realpathSync } from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
|
-
import { buildFailure,
|
|
5
|
+
import { buildFailure, inspectBuildStatus } from "./build-status.mjs";
|
|
6
6
|
|
|
7
7
|
function stamp(file) {
|
|
8
8
|
try { const stat = statSync(file); return `${stat.mtimeMs}:${stat.ctimeMs}:${stat.size}`; }
|
|
@@ -13,7 +13,7 @@ function stamp(file) {
|
|
|
13
13
|
export function createPreviewWatch(root, config, { log = process.stderr, buildTimeout = 30000 } = {}) {
|
|
14
14
|
config = { ...config, apps: [...config.apps] };
|
|
15
15
|
const command = config.previewWatch;
|
|
16
|
-
|
|
16
|
+
let session = randomUUID();
|
|
17
17
|
let state = command ? "idle" : "disabled";
|
|
18
18
|
let child;
|
|
19
19
|
let error;
|
|
@@ -21,11 +21,14 @@ export function createPreviewWatch(root, config, { log = process.stderr, buildTi
|
|
|
21
21
|
let childClosed = false;
|
|
22
22
|
let killTimer;
|
|
23
23
|
let stopping;
|
|
24
|
+
let restarting;
|
|
25
|
+
let disposed = false;
|
|
24
26
|
const baselines = new Map();
|
|
25
27
|
const ready = new Set();
|
|
26
28
|
const requests = new Set();
|
|
27
29
|
let active;
|
|
28
|
-
|
|
30
|
+
let requestFile = path.join(root, ".ds", `preview-requests-${session}.json`);
|
|
31
|
+
let reportFile = path.join(root, ".ds", `build-status-${session}.json`);
|
|
29
32
|
function request(file) {
|
|
30
33
|
if (!command || requests.has(file) && active === file) return;
|
|
31
34
|
requests.add(file);
|
|
@@ -48,7 +51,7 @@ export function createPreviewWatch(root, config, { log = process.stderr, buildTi
|
|
|
48
51
|
} catch (failure) { if (failure.code !== "ESRCH") throw failure; }
|
|
49
52
|
}
|
|
50
53
|
function start() {
|
|
51
|
-
if (state !== "idle") return;
|
|
54
|
+
if (disposed || state !== "idle") return;
|
|
52
55
|
for (const app of config.apps) {
|
|
53
56
|
const file = path.resolve(root, app.file);
|
|
54
57
|
baselines.set(file, stamp(file));
|
|
@@ -57,7 +60,7 @@ export function createPreviewWatch(root, config, { log = process.stderr, buildTi
|
|
|
57
60
|
// No shell: configured arguments remain literal. Child stdin cannot consume
|
|
58
61
|
// the MCP channel, and both output streams go exclusively to diagnostics.
|
|
59
62
|
const executable = command.command === "node" ? process.execPath : command.command;
|
|
60
|
-
const env = { ...process.env, ADS_MCP_BUILD_SESSION: session, ADS_MCP_BUILD_STATUS:
|
|
63
|
+
const env = { ...process.env, ADS_MCP_BUILD_SESSION: session, ADS_MCP_BUILD_STATUS: reportFile, ADS_MCP_REQUEST_FILE: requestFile, PATH: [path.join(root, "node_modules/.bin"), path.dirname(process.execPath), process.env.PATH ?? ""].join(path.delimiter) };
|
|
61
64
|
try {
|
|
62
65
|
child = spawn(executable, command.args, { cwd: root, env, shell: false, detached: process.platform !== "win32", stdio: ["ignore", "pipe", "pipe"], windowsHide: true });
|
|
63
66
|
} catch (failure) {
|
|
@@ -76,7 +79,7 @@ export function createPreviewWatch(root, config, { log = process.stderr, buildTi
|
|
|
76
79
|
clearTimeout(killTimer);
|
|
77
80
|
if (state !== "stopping") {
|
|
78
81
|
state = "failed";
|
|
79
|
-
error ??= `Preview watcher exited (${signal ?? code}).
|
|
82
|
+
error ??= `Preview watcher exited (${signal ?? code}). Fix the builder and invoke the preview again to restart it; MCP inspection remains available.`;
|
|
80
83
|
log.write(`ads mcp: ${error}\n`);
|
|
81
84
|
} else state = "stopped";
|
|
82
85
|
resolve();
|
|
@@ -91,16 +94,36 @@ export function createPreviewWatch(root, config, { log = process.stderr, buildTi
|
|
|
91
94
|
child.once("error", (failure) => { error = `Cannot start preview watcher: ${failure.message}`; });
|
|
92
95
|
}
|
|
93
96
|
|
|
97
|
+
function restart() {
|
|
98
|
+
return restarting ??= (async () => {
|
|
99
|
+
await stopChild();
|
|
100
|
+
if (disposed) return;
|
|
101
|
+
session = randomUUID();
|
|
102
|
+
requestFile = path.join(root, ".ds", `preview-requests-${session}.json`);
|
|
103
|
+
reportFile = path.join(root, ".ds", `build-status-${session}.json`);
|
|
104
|
+
state = "idle"; error = undefined; child = closed = stopping = undefined; childClosed = false;
|
|
105
|
+
ready.clear(); baselines.clear(); active = undefined;
|
|
106
|
+
start();
|
|
107
|
+
})().finally(() => { restarting = undefined; });
|
|
108
|
+
}
|
|
109
|
+
|
|
94
110
|
async function ensure(file, signal) {
|
|
111
|
+
if (signal?.aborted) throw new Error("Preview request cancelled.");
|
|
112
|
+
if (state === "failed" || restarting) await restart();
|
|
113
|
+
if (disposed) throw new Error("Preview watcher is stopped.");
|
|
95
114
|
start();
|
|
96
115
|
request(file);
|
|
97
116
|
if (signal?.aborted) throw new Error("Preview request cancelled.");
|
|
98
117
|
const app = config.apps.find((entry) => path.resolve(root, entry.file) === file);
|
|
99
|
-
|
|
118
|
+
let baseline = baselines.get(file) ?? null;
|
|
100
119
|
const deadline = Date.now() + buildTimeout;
|
|
120
|
+
let restarted = false;
|
|
101
121
|
while (Date.now() < deadline) {
|
|
102
122
|
if (signal?.aborted) throw new Error("Preview request cancelled.");
|
|
103
123
|
const build = buildStatus().previews.find((entry) => entry.id === app?.id);
|
|
124
|
+
if (!restarted && build?.diagnostics.some(item => item.code === "ADS_PREVIEW_RESTART")) {
|
|
125
|
+
restarted = true; await restart(); request(file); baseline = baselines.get(file) ?? null; continue;
|
|
126
|
+
}
|
|
104
127
|
if (build?.state === "error") throw buildFailure(build);
|
|
105
128
|
if (command && state !== "running") throw new Error(error ?? "Preview watcher is stopped.");
|
|
106
129
|
if (build?.current) return build;
|
|
@@ -113,23 +136,23 @@ export function createPreviewWatch(root, config, { log = process.stderr, buildTi
|
|
|
113
136
|
}
|
|
114
137
|
|
|
115
138
|
const status = () => ({ state, ...(child?.pid ? { pid: child.pid } : {}), ...(error ? { error } : {}) });
|
|
116
|
-
const buildStatus = () => inspectBuildStatus(root, config, command ? { session, processState: status() } : {});
|
|
139
|
+
const buildStatus = () => inspectBuildStatus(root, config, command ? { session, file: reportFile, processState: status() } : {});
|
|
117
140
|
|
|
118
|
-
async function
|
|
141
|
+
async function stopChild() {
|
|
119
142
|
rmSync(requestFile, { force: true });
|
|
120
143
|
if (stopping) return stopping;
|
|
121
|
-
if (!child || childClosed) { if (command) state = "stopped"; return; }
|
|
144
|
+
if (!child || childClosed) { if (command) state = "stopped"; rmSync(reportFile, { force: true }); return; }
|
|
122
145
|
if (state === "stopped") return;
|
|
123
146
|
state = "stopping";
|
|
124
147
|
stopping = (async () => {
|
|
125
148
|
kill("SIGTERM");
|
|
126
149
|
clearTimeout(killTimer);
|
|
127
150
|
const timer = setTimeout(() => kill("SIGKILL"), 1500);
|
|
128
|
-
try { await closed; } finally { clearTimeout(timer); }
|
|
151
|
+
try { await closed; } finally { clearTimeout(timer); rmSync(reportFile, { force: true }); }
|
|
129
152
|
})();
|
|
130
153
|
return stopping;
|
|
131
154
|
}
|
|
132
|
-
return { start, ensure, stop, kill: () => kill("SIGKILL"), status, buildStatus, setApps(apps) {
|
|
155
|
+
return { start, ensure, async stop() { disposed = true; await restarting; return stopChild(); }, kill: () => kill("SIGKILL"), status, buildStatus, setApps(apps) {
|
|
133
156
|
for (const app of apps) {
|
|
134
157
|
const file = path.resolve(root, app.file);
|
|
135
158
|
if (!baselines.has(file)) baselines.set(file, stamp(file));
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
-
import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, watch, writeFileSync } from "node:fs";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, watch, writeFileSync } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { loadConfig, isInside } from "./config.mjs";
|
|
5
5
|
import { inspectScenarios } from "./scenarios.mjs";
|
|
@@ -71,7 +71,7 @@ export function createPreviewCache(options, compilerFiles = []) {
|
|
|
71
71
|
}
|
|
72
72
|
return { inputs, reusable, validated: stamp => value.validation === stamp,
|
|
73
73
|
assertCompiler() {
|
|
74
|
-
if (adapterDigest() !== loadedAdapter) throw new Error("Preview compiler code or dependencies changed.
|
|
74
|
+
if (adapterDigest() !== loadedAdapter) throw Object.assign(new Error("Preview compiler code or dependencies changed. Invoke the preview again to restart the builder and load current code."), { code: "ADS_PREVIEW_RESTART" });
|
|
75
75
|
},
|
|
76
76
|
validate(stamp) { value.validation = stamp; save(); },
|
|
77
77
|
record(entry, dependencies) {
|
|
@@ -93,12 +93,17 @@ export async function runScenarioCompiler(directory, { watched = false, validate
|
|
|
93
93
|
function registry() {
|
|
94
94
|
options = loadConfig(root);
|
|
95
95
|
const result = inspectScenarios(options);
|
|
96
|
-
if (result.diagnostics.length) throw new Error(result.diagnostics.map(item => `${item.file}: ${item.message}`).join("\n"));
|
|
96
|
+
if (result.diagnostics.length) throw Object.assign(new Error(result.diagnostics.map(item => `${item.file}: ${item.message}`).join("\n")), { scenarios: result.scenarios });
|
|
97
97
|
mkdirSync(path.join(root, ".ds/scenarios"), { recursive: true });
|
|
98
98
|
writeFileSync(path.join(root, ".ds/scenarios/manifest.json"), JSON.stringify({ schemaVersion: 1, scenarios: result.scenarios }, null, 2));
|
|
99
99
|
return result.scenarios;
|
|
100
100
|
}
|
|
101
|
-
let entries =
|
|
101
|
+
let entries = [], registryFailure;
|
|
102
|
+
try { entries = registry(); }
|
|
103
|
+
catch (error) {
|
|
104
|
+
if (!watched) throw error;
|
|
105
|
+
registryFailure = error; entries = error.scenarios ?? [];
|
|
106
|
+
}
|
|
102
107
|
const reporter = createBuildReporter(root, { apps: entries.map(entry => ({ id: entry.id, file: entry.artifact })) });
|
|
103
108
|
const requested = new Set();
|
|
104
109
|
const pending = new Set();
|
|
@@ -108,6 +113,12 @@ export async function runScenarioCompiler(directory, { watched = false, validate
|
|
|
108
113
|
let generation = 0;
|
|
109
114
|
let publishedStamp;
|
|
110
115
|
let validation;
|
|
116
|
+
function failure(error) {
|
|
117
|
+
try { reporter.failure(error); } catch (reportError) { console.error(reportError); }
|
|
118
|
+
console.error(error);
|
|
119
|
+
if (!watched) process.exitCode = 1;
|
|
120
|
+
}
|
|
121
|
+
if (registryFailure) failure(registryFailure);
|
|
111
122
|
function gate(stamp) {
|
|
112
123
|
if (cache.validated(stamp)) return Promise.resolve();
|
|
113
124
|
return validation ??= Promise.resolve().then(validate).then(() => {
|
|
@@ -119,8 +130,8 @@ export async function runScenarioCompiler(directory, { watched = false, validate
|
|
|
119
130
|
if (buildGraph) return compileGraph();
|
|
120
131
|
const revision = reporter.begin(entry.id);
|
|
121
132
|
const started = generation;
|
|
122
|
-
const stamp = cache.inputs();
|
|
123
133
|
try {
|
|
134
|
+
const stamp = cache.inputs();
|
|
124
135
|
cache.assertCompiler();
|
|
125
136
|
const reuse = cache.reusable(entry);
|
|
126
137
|
const dependencies = new Set();
|
|
@@ -147,7 +158,7 @@ export async function runScenarioCompiler(directory, { watched = false, validate
|
|
|
147
158
|
const failure = outcomes.find(outcome => outcome.status === "rejected");
|
|
148
159
|
if (failure) throw failure.reason;
|
|
149
160
|
cache.assertCompiler();
|
|
150
|
-
if (started !== generation || stamp !== cache.inputs()) return; // Watch queued a newer revision.
|
|
161
|
+
if (stopped || started !== generation || stamp !== cache.inputs()) return; // Watch queued a newer revision.
|
|
151
162
|
if (!reuse) cache.record(entry, dependencies);
|
|
152
163
|
reporter.ready(entry.id, revision);
|
|
153
164
|
} catch (error) {
|
|
@@ -160,24 +171,24 @@ export async function runScenarioCompiler(directory, { watched = false, validate
|
|
|
160
171
|
async function compileGraph() {
|
|
161
172
|
const revisions = new Map(entries.map(entry => [entry.id, reporter.begin(entry.id)]));
|
|
162
173
|
const started = generation;
|
|
163
|
-
const stamp = cache.inputs();
|
|
164
|
-
const dependencies = new Set();
|
|
165
|
-
function assets(directory) {
|
|
166
|
-
for (const item of readdirSync(directory, { withFileTypes: true })) {
|
|
167
|
-
if (item.isSymbolicLink() || ["node_modules", ".ds", ".vite", "dist"].includes(item.name)) continue;
|
|
168
|
-
const file = path.join(directory, item.name);
|
|
169
|
-
if (item.isDirectory()) assets(file);
|
|
170
|
-
else if (/\.(?:css|svg|png|jpe?g|webp|gif|woff2?|ttf|otf)$/.test(file)) dependencies.add(file);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
assets(options.dsRoot);
|
|
174
|
-
const track = { name: "ads-preview-graph-dependencies", generateBundle() {
|
|
175
|
-
for (const id of this.getModuleIds()) {
|
|
176
|
-
const file = id.split("?")[0];
|
|
177
|
-
if (path.isAbsolute(file) && existsSync(file)) dependencies.add(file);
|
|
178
|
-
}
|
|
179
|
-
} };
|
|
180
174
|
try {
|
|
175
|
+
const stamp = cache.inputs();
|
|
176
|
+
const dependencies = new Set();
|
|
177
|
+
function assets(directory) {
|
|
178
|
+
for (const item of readdirSync(directory, { withFileTypes: true })) {
|
|
179
|
+
if (item.isSymbolicLink() || ["node_modules", ".ds", ".vite", "dist"].includes(item.name)) continue;
|
|
180
|
+
const file = path.join(directory, item.name);
|
|
181
|
+
if (item.isDirectory()) assets(file);
|
|
182
|
+
else if (/\.(?:css|svg|png|jpe?g|webp|gif|woff2?|ttf|otf)$/.test(file)) dependencies.add(file);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
assets(options.dsRoot);
|
|
186
|
+
const track = { name: "ads-preview-graph-dependencies", generateBundle() {
|
|
187
|
+
for (const id of this.getModuleIds()) {
|
|
188
|
+
const file = id.split("?")[0];
|
|
189
|
+
if (path.isAbsolute(file) && existsSync(file)) dependencies.add(file);
|
|
190
|
+
}
|
|
191
|
+
} };
|
|
181
192
|
cache.assertCompiler();
|
|
182
193
|
let reusable = entries.every(entry => cache.reusable(entry));
|
|
183
194
|
if (reusable) { try { reusable = Boolean(readPreviewManifest(options, { verify: true })); } catch { reusable = false; } }
|
|
@@ -185,7 +196,7 @@ export async function runScenarioCompiler(directory, { watched = false, validate
|
|
|
185
196
|
const failure = outcomes.find(outcome => outcome.status === "rejected");
|
|
186
197
|
if (failure) throw failure.reason;
|
|
187
198
|
cache.assertCompiler();
|
|
188
|
-
if (started !== generation || stamp !== cache.inputs()) return;
|
|
199
|
+
if (stopped || started !== generation || stamp !== cache.inputs()) return;
|
|
189
200
|
const publication = outcomes[1].value;
|
|
190
201
|
if (publication) {
|
|
191
202
|
publication.publish(reporter.session);
|
|
@@ -209,15 +220,16 @@ export async function runScenarioCompiler(directory, { watched = false, validate
|
|
|
209
220
|
if (compiling) return;
|
|
210
221
|
compiling = true;
|
|
211
222
|
try {
|
|
212
|
-
while (pending.size && !stopped) {
|
|
223
|
+
while (pending.size && !registryFailure && !stopped) {
|
|
213
224
|
const id = pending.has(active) ? active : pending.values().next().value;
|
|
214
225
|
pending.delete(id);
|
|
215
226
|
const entry = entries.find(entry => entry.id === id);
|
|
216
227
|
if (entry) await compile(entry);
|
|
217
228
|
}
|
|
218
|
-
}
|
|
229
|
+
} catch (error) { failure(error); }
|
|
230
|
+
finally { compiling = false; }
|
|
219
231
|
}
|
|
220
|
-
function request() {
|
|
232
|
+
function request(startBuild = true) {
|
|
221
233
|
const file = process.env.ADS_MCP_REQUEST_FILE;
|
|
222
234
|
if (!file || !existsSync(file)) return;
|
|
223
235
|
try {
|
|
@@ -231,8 +243,8 @@ export async function runScenarioCompiler(directory, { watched = false, validate
|
|
|
231
243
|
if (!buildGraph || publishedStamp !== cache.inputs()) pending.add(entry.id);
|
|
232
244
|
}
|
|
233
245
|
}
|
|
234
|
-
void drain();
|
|
235
|
-
} catch (error) {
|
|
246
|
+
if (startBuild) void drain();
|
|
247
|
+
} catch (error) { failure(error); }
|
|
236
248
|
}
|
|
237
249
|
if (!watched) {
|
|
238
250
|
for (const entry of entries) pending.add(entry.id);
|
|
@@ -241,8 +253,8 @@ export async function runScenarioCompiler(directory, { watched = false, validate
|
|
|
241
253
|
}
|
|
242
254
|
let timer;
|
|
243
255
|
const changed = new Set();
|
|
244
|
-
function sourceChanged(_event, file) {
|
|
245
|
-
if (!file || !/\.(?:[cm]?[jt]sx?|css|html|json|md|svg|png|jpe?g|webp|gif|woff2?|ttf|otf|lock|lockb)$/.test(file)) return;
|
|
256
|
+
function sourceChanged(_event, file, topology = false) {
|
|
257
|
+
if (!file || !topology && !/\.(?:[cm]?[jt]sx?|css|html|json|md|svg|png|jpe?g|webp|gif|woff2?|ttf|otf|lock|lockb)$/.test(file)) return;
|
|
246
258
|
const absolute = path.join(root, file);
|
|
247
259
|
if (file.split(path.sep).some(name => ["node_modules", ".git", ".ds", ".vite", "coverage"].includes(name)) || options.config.assets.generatedPaths.some(base => isInside(path.resolve(root, base), absolute))) return;
|
|
248
260
|
changed.add(absolute);
|
|
@@ -253,7 +265,10 @@ export async function runScenarioCompiler(directory, { watched = false, validate
|
|
|
253
265
|
const changes = [...changed]; changed.clear();
|
|
254
266
|
try {
|
|
255
267
|
entries = registry();
|
|
268
|
+
registryFailure = undefined;
|
|
256
269
|
reporter.setApps(entries.map(entry => ({ id: entry.id, file: entry.artifact })));
|
|
270
|
+
reporter.clearFailure();
|
|
271
|
+
request(false);
|
|
257
272
|
for (const id of requested) if (!entries.some(entry => entry.id === id)) { requested.delete(id); pending.delete(id); }
|
|
258
273
|
// Every source change needs validation; unaffected artifacts need no
|
|
259
274
|
// compilation. Check active first, then rebuild only changed dependencies.
|
|
@@ -263,25 +278,53 @@ export async function runScenarioCompiler(directory, { watched = false, validate
|
|
|
263
278
|
}
|
|
264
279
|
if (changes.length) void drain();
|
|
265
280
|
} catch (error) {
|
|
266
|
-
|
|
267
|
-
console.error(error);
|
|
281
|
+
registryFailure = error; failure(error);
|
|
268
282
|
}
|
|
269
283
|
}, 100);
|
|
270
284
|
}
|
|
271
285
|
// Do not allocate recursive OS watches for node_modules or generated trees.
|
|
272
|
-
const watchers =
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
286
|
+
const watchers = new Map();
|
|
287
|
+
function addWatch(directory, callback, recursive = false) {
|
|
288
|
+
if (stopped) return false;
|
|
289
|
+
try {
|
|
290
|
+
const stat = statSync(directory), identity = `${stat.dev}:${stat.ino}`;
|
|
291
|
+
if (watchers.get(directory)?.identity === identity) return false;
|
|
292
|
+
watchers.get(directory)?.watcher.close();
|
|
293
|
+
const watcher = watch(directory, { recursive }, callback);
|
|
294
|
+
watchers.set(directory, { watcher, identity });
|
|
295
|
+
watcher.on("error", error => {
|
|
296
|
+
watcher.close();
|
|
297
|
+
if (watchers.get(directory)?.watcher === watcher) watchers.delete(directory);
|
|
298
|
+
failure(error);
|
|
299
|
+
});
|
|
300
|
+
return true;
|
|
301
|
+
} catch (error) { failure(error); }
|
|
278
302
|
}
|
|
279
|
-
|
|
280
|
-
if (
|
|
281
|
-
|
|
303
|
+
function reconcileWatches(initial = false) {
|
|
304
|
+
if (stopped) return;
|
|
305
|
+
try {
|
|
306
|
+
addWatch(root, sourceChanged);
|
|
307
|
+
const wanted = new Set([root, path.join(root, ".ds")]);
|
|
308
|
+
for (const entry of readdirSync(root, { withFileTypes: true })) {
|
|
309
|
+
const directory = path.join(root, entry.name);
|
|
310
|
+
if (!entry.isDirectory() || entry.isSymbolicLink() || ["node_modules", ".git", ".ds", ".vite", "coverage"].includes(entry.name)
|
|
311
|
+
|| options.config.assets.generatedPaths.some(base => isInside(path.resolve(root, base), directory))) continue;
|
|
312
|
+
wanted.add(directory);
|
|
313
|
+
if (addWatch(directory, (event, file) => sourceChanged(event, file && path.join(entry.name, file)), true) && !initial) sourceChanged("rename", entry.name, true);
|
|
314
|
+
}
|
|
315
|
+
mkdirSync(path.join(root, ".ds"), { recursive: true });
|
|
316
|
+
addWatch(path.join(root, ".ds"), (_event, file) => { if (file === path.basename(process.env.ADS_MCP_REQUEST_FILE ?? "")) request(); });
|
|
317
|
+
for (const [directory, { watcher }] of watchers) if (!wanted.has(directory) || !existsSync(directory)) {
|
|
318
|
+
watcher.close(); watchers.delete(directory);
|
|
319
|
+
if (!initial && directory !== root && directory !== path.join(root, ".ds")) sourceChanged("rename", path.relative(root, directory), true);
|
|
320
|
+
}
|
|
321
|
+
} catch (error) { failure(error); }
|
|
322
|
+
}
|
|
323
|
+
reconcileWatches(true);
|
|
324
|
+
const reconcileTimer = setInterval(reconcileWatches, 1000);
|
|
282
325
|
request();
|
|
283
326
|
console.log(`Scenario compiler ready: ${entries.length} entries; builds are requested on demand.`);
|
|
284
327
|
for (const signal of ["SIGINT", "SIGTERM"]) process.once(signal, () => {
|
|
285
|
-
stopped = true; clearTimeout(timer); for (const watcher of watchers
|
|
328
|
+
stopped = true; clearTimeout(timer); clearInterval(reconcileTimer); for (const { watcher } of watchers.values()) watcher.close();
|
|
286
329
|
});
|
|
287
330
|
}
|
|
@@ -251,6 +251,25 @@ Use local simulated services in fixtures. Tests may import fixtures; load real
|
|
|
251
251
|
foundation CSS rather than assigning shared token values in JavaScript. Keep
|
|
252
252
|
full-app integration harnesses and workflow coverage in application test support.
|
|
253
253
|
|
|
254
|
+
Foundation CSS is checked, not an escape hatch. Value sheets define shared tokens
|
|
255
|
+
on the document root/themes; `fonts.css` holds font-face metadata. `base.css` has
|
|
256
|
+
three closed roles: root/body typography, canvas and theme defaults; limited native
|
|
257
|
+
normalization; and global focus indicators/reduced-motion rules. Every selector
|
|
258
|
+
branch, property and value must fit its role. Token-backed control skins, layout,
|
|
259
|
+
interactive state rules and owner hooks are rejected even through `:where()`.
|
|
260
|
+
Do not copy an unchecked upstream preflight or turn a component repair into a reset.
|
|
261
|
+
Put skin/state/animation in the Linaria owner, and scenario layout variations in
|
|
262
|
+
the public review pattern's typed API. Browser review still verifies the effects.
|
|
263
|
+
|
|
264
|
+
Invalid metadata and source failures keep the managed compiler alive for repairs.
|
|
265
|
+
Read `ads_status.build` or `ads://project/build`, fix the owner, then wait for current
|
|
266
|
+
successful output. If the builder exited, fix its command and invoke the preview
|
|
267
|
+
again; ADS starts one fresh child without an MCP reconnect. Compiler-code changes
|
|
268
|
+
also reload on the next preview invocation. Reconnect the client when upgrading
|
|
269
|
+
the ADS server package itself. Persistent SSE/notification streams make
|
|
270
|
+
`networkidle` unsuitable: wait for the App's current ready/error state instead.
|
|
271
|
+
|
|
272
|
+
|
|
254
273
|
|
|
255
274
|
|
|
256
275
|
Register component/project Oxlint plugins and rules in `ds.config.json`. Use
|
|
@@ -55,7 +55,7 @@ If no published distribution exists, build and pack the tooling checkout with
|
|
|
55
55
|
From inside an existing test project:
|
|
56
56
|
|
|
57
57
|
```sh
|
|
58
|
-
ads_tool_package="/absolute/path/to/reqvire-org-ads-0.3.
|
|
58
|
+
ads_tool_package="/absolute/path/to/reqvire-org-ads-0.3.2.tgz"
|
|
59
59
|
npx --yes --package "$ads_tool_package" ads --help
|
|
60
60
|
npx --yes --package "$ads_tool_package" ads init --framework react --tool-package "$ads_tool_package"
|
|
61
61
|
npm install
|
|
@@ -93,7 +93,8 @@ component rules through the project's plugin adapter and `ds.config.json`, then
|
|
|
93
93
|
run its actual build. Use `lint-config` directly for custom pipeline integration
|
|
94
94
|
or debugging, not as a separate routine setup step.
|
|
95
95
|
|
|
96
|
-
Use ADS 0.3.
|
|
96
|
+
Use ADS 0.3.2 or later for the closed foundation contract and recoverable
|
|
97
|
+
shared-resource preview compiler. `mcp` starts stdio
|
|
97
98
|
plus its local website; `showcase` starts the browser-only MCP runtime. Open the existing runtime URL from `ads_status` when
|
|
98
99
|
connected; otherwise use `npm run dev:showcase` or the installed `ads showcase`.
|
|
99
100
|
The tool client discovers entries and renders MCP Apps. Init adds the launch
|
|
@@ -203,6 +203,25 @@ Use local simulated services in fixtures. Tests may import fixtures; load real
|
|
|
203
203
|
foundation CSS rather than assigning shared token values in JavaScript. Keep
|
|
204
204
|
full-app integration harnesses and workflow coverage in application test support.
|
|
205
205
|
|
|
206
|
+
Foundation CSS is checked, not an escape hatch. Value sheets define shared tokens
|
|
207
|
+
on the document root/themes; `fonts.css` holds font-face metadata. `base.css` has
|
|
208
|
+
three closed roles: root/body typography, canvas and theme defaults; limited native
|
|
209
|
+
normalization; and global focus indicators/reduced-motion rules. Every selector
|
|
210
|
+
branch, property and value must fit its role. Token-backed control skins, layout,
|
|
211
|
+
interactive state rules and owner hooks are rejected even through `:where()`.
|
|
212
|
+
Do not copy an unchecked upstream preflight or turn a component repair into a reset.
|
|
213
|
+
Put skin/state/animation in the Linaria owner, and scenario layout variations in
|
|
214
|
+
the public review pattern's typed API. Browser review still verifies the effects.
|
|
215
|
+
|
|
216
|
+
Invalid metadata and source failures keep the managed compiler alive for repairs.
|
|
217
|
+
Read `ads_status.build` or `ads://project/build`, fix the owner, then wait for current
|
|
218
|
+
successful output. If the builder exited, fix its command and invoke the preview
|
|
219
|
+
again; ADS starts one fresh child without an MCP reconnect. Compiler-code changes
|
|
220
|
+
also reload on the next preview invocation. Reconnect the client when upgrading
|
|
221
|
+
the ADS server package itself. Persistent SSE/notification streams make
|
|
222
|
+
`networkidle` unsuitable: wait for the App's current ready/error state instead.
|
|
223
|
+
|
|
224
|
+
|
|
206
225
|
|
|
207
226
|
|
|
208
227
|
Register component/project Oxlint plugins and rules in `ds.config.json`. Use
|
|
@@ -341,6 +341,25 @@ Use local simulated services in fixtures. Tests may import fixtures; load real
|
|
|
341
341
|
foundation CSS rather than assigning shared token values in JavaScript. Keep
|
|
342
342
|
full-app integration harnesses and workflow coverage in application test support.
|
|
343
343
|
|
|
344
|
+
Foundation CSS is checked, not an escape hatch. Value sheets define shared tokens
|
|
345
|
+
on the document root/themes; `fonts.css` holds font-face metadata. `base.css` has
|
|
346
|
+
three closed roles: root/body typography, canvas and theme defaults; limited native
|
|
347
|
+
normalization; and global focus indicators/reduced-motion rules. Every selector
|
|
348
|
+
branch, property and value must fit its role. Token-backed control skins, layout,
|
|
349
|
+
interactive state rules and owner hooks are rejected even through `:where()`.
|
|
350
|
+
Do not copy an unchecked upstream preflight or turn a component repair into a reset.
|
|
351
|
+
Put skin/state/animation in the Linaria owner, and scenario layout variations in
|
|
352
|
+
the public review pattern's typed API. Browser review still verifies the effects.
|
|
353
|
+
|
|
354
|
+
Invalid metadata and source failures keep the managed compiler alive for repairs.
|
|
355
|
+
Read `ads_status.build` or `ads://project/build`, fix the owner, then wait for current
|
|
356
|
+
successful output. If the builder exited, fix its command and invoke the preview
|
|
357
|
+
again; ADS starts one fresh child without an MCP reconnect. Compiler-code changes
|
|
358
|
+
also reload on the next preview invocation. Reconnect the client when upgrading
|
|
359
|
+
the ADS server package itself. Persistent SSE/notification streams make
|
|
360
|
+
`networkidle` unsuitable: wait for the App's current ready/error state instead.
|
|
361
|
+
|
|
362
|
+
|
|
344
363
|
|
|
345
364
|
|
|
346
365
|
Register component/project Oxlint plugins and rules in `ds.config.json`. Use
|
|
@@ -40,6 +40,35 @@ Token names and prefixes are fixed by ADS architecture; foreign brand prefixes
|
|
|
40
40
|
are rejected. Shared assignments belong in token files. Components own
|
|
41
41
|
`--ds-*`, patterns `--ux-*`, and the showcase `--showcase-*`; token files cannot
|
|
42
42
|
assign those local variables or override component state.
|
|
43
|
+
|
|
44
|
+
Foundation roles are enforced by `foundation-role`, `foundation-selector` and
|
|
45
|
+
`foundation-import`, alongside the regular token checks. Shared definitions
|
|
46
|
+
belong on the document root or explicit root themes; ordinary declarations belong
|
|
47
|
+
in `base.css`, and font-face descriptors in optional `fonts.css`. The entry imports
|
|
48
|
+
canonical sheets; those sheets cannot import another styling layer. Component
|
|
49
|
+
classes, IDs, data/ARIA hooks, contextual selectors, keyframes and scoped UI rules
|
|
50
|
+
are rejected in the foundation, including nested functional selectors. Raw visual
|
|
51
|
+
values and undefined token references are checked in native defaults too; literal
|
|
52
|
+
font-face metadata keeps its separate asset role.
|
|
53
|
+
|
|
54
|
+
`base.css` has three closed roles:
|
|
55
|
+
|
|
56
|
+
- Document defaults: root/body typography, canvas colors and theme behavior,
|
|
57
|
+
global text selection and document scrollbars.
|
|
58
|
+
- Native resets: permitted normalization properties and values, such as zero
|
|
59
|
+
spacing, inherited control typography, list resets and responsive replaced media.
|
|
60
|
+
- Global accessibility: universal `:focus-visible` outline rules, and reduced-motion
|
|
61
|
+
overrides inside `@media (prefers-reduced-motion: reduce)`.
|
|
62
|
+
|
|
63
|
+
The `foundation-role` check validates every selector branch, property and value.
|
|
64
|
+
Native selectors are not a bypass: token-backed button backgrounds, flex/grid
|
|
65
|
+
layout, component-specific focus rules and hover/disabled skins are rejected.
|
|
66
|
+
Shared token definitions belong in value sheets, not `base.css`. No project token
|
|
67
|
+
names are embedded in this policy. A zero-specificity `:where()` wrapper does not
|
|
68
|
+
change ownership. Put component design and animation in Linaria owners, and
|
|
69
|
+
scenario layout variations in typed public review-pattern props. Browser review
|
|
70
|
+
still checks the effects and accessibility of permitted defaults.
|
|
71
|
+
|
|
43
72
|
Own primitive markup, focus, hover, selected, disabled, loading, and motion in
|
|
44
73
|
the primitive. Use typed props for variations. Keep reusable structure and skin
|
|
45
74
|
in colocated Linaria blocks using `css` and `cx` from `@linaria/atomic`.
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
:root { color-scheme: light; }
|
|
2
|
+
:root[data-theme="dark"] { color-scheme: dark; }
|
|
3
|
+
|
|
1
4
|
*,
|
|
2
5
|
*::before,
|
|
3
6
|
*::after {
|
|
@@ -5,7 +8,7 @@
|
|
|
5
8
|
}
|
|
6
9
|
|
|
7
10
|
body {
|
|
8
|
-
margin:
|
|
11
|
+
margin: 0;
|
|
9
12
|
background: var(--bg-canvas);
|
|
10
13
|
color: var(--text-body);
|
|
11
14
|
font-family: var(--font-sans);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
:root {
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
--bg-canvas: #faf9f6;
|
|
4
4
|
--bg-surface: #fff;
|
|
5
5
|
--bg-hover: #f1efea;
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
:root[data-theme="dark"] {
|
|
18
|
-
|
|
18
|
+
|
|
19
19
|
--bg-canvas: #17191e;
|
|
20
20
|
--bg-surface: #20232a;
|
|
21
21
|
--bg-hover: #2c3039;
|
|
@@ -55,7 +55,7 @@ If no published distribution exists, build and pack the tooling checkout with
|
|
|
55
55
|
From inside an existing test project:
|
|
56
56
|
|
|
57
57
|
```sh
|
|
58
|
-
ads_tool_package="/absolute/path/to/reqvire-org-ads-0.3.
|
|
58
|
+
ads_tool_package="/absolute/path/to/reqvire-org-ads-0.3.2.tgz"
|
|
59
59
|
npx --yes --package "$ads_tool_package" ads --help
|
|
60
60
|
npx --yes --package "$ads_tool_package" ads init --framework react --tool-package "$ads_tool_package"
|
|
61
61
|
npm install
|
|
@@ -93,7 +93,8 @@ component rules through the project's plugin adapter and `ds.config.json`, then
|
|
|
93
93
|
run its actual build. Use `lint-config` directly for custom pipeline integration
|
|
94
94
|
or debugging, not as a separate routine setup step.
|
|
95
95
|
|
|
96
|
-
Use ADS 0.3.
|
|
96
|
+
Use ADS 0.3.2 or later for the closed foundation contract and recoverable
|
|
97
|
+
shared-resource preview compiler. `mcp` starts stdio
|
|
97
98
|
plus its local website; `showcase` starts the browser-only MCP runtime. Open the existing runtime URL from `ads_status` when
|
|
98
99
|
connected; otherwise use `npm run dev:showcase` or the installed `ads showcase`.
|
|
99
100
|
The tool client discovers entries and renders MCP Apps. Init adds the launch
|
|
@@ -308,6 +308,25 @@ Use local simulated services in fixtures. Tests may import fixtures; load real
|
|
|
308
308
|
foundation CSS rather than assigning shared token values in JavaScript. Keep
|
|
309
309
|
full-app integration harnesses and workflow coverage in application test support.
|
|
310
310
|
|
|
311
|
+
Foundation CSS is checked, not an escape hatch. Value sheets define shared tokens
|
|
312
|
+
on the document root/themes; `fonts.css` holds font-face metadata. `base.css` has
|
|
313
|
+
three closed roles: root/body typography, canvas and theme defaults; limited native
|
|
314
|
+
normalization; and global focus indicators/reduced-motion rules. Every selector
|
|
315
|
+
branch, property and value must fit its role. Token-backed control skins, layout,
|
|
316
|
+
interactive state rules and owner hooks are rejected even through `:where()`.
|
|
317
|
+
Do not copy an unchecked upstream preflight or turn a component repair into a reset.
|
|
318
|
+
Put skin/state/animation in the Linaria owner, and scenario layout variations in
|
|
319
|
+
the public review pattern's typed API. Browser review still verifies the effects.
|
|
320
|
+
|
|
321
|
+
Invalid metadata and source failures keep the managed compiler alive for repairs.
|
|
322
|
+
Read `ads_status.build` or `ads://project/build`, fix the owner, then wait for current
|
|
323
|
+
successful output. If the builder exited, fix its command and invoke the preview
|
|
324
|
+
again; ADS starts one fresh child without an MCP reconnect. Compiler-code changes
|
|
325
|
+
also reload on the next preview invocation. Reconnect the client when upgrading
|
|
326
|
+
the ADS server package itself. Persistent SSE/notification streams make
|
|
327
|
+
`networkidle` unsuitable: wait for the App's current ready/error state instead.
|
|
328
|
+
|
|
329
|
+
|
|
311
330
|
|
|
312
331
|
|
|
313
332
|
Register component/project Oxlint plugins and rules in `ds.config.json`. Use
|
|
@@ -55,7 +55,7 @@ If no published distribution exists, build and pack the tooling checkout with
|
|
|
55
55
|
From inside an existing test project:
|
|
56
56
|
|
|
57
57
|
```sh
|
|
58
|
-
ads_tool_package="/absolute/path/to/reqvire-org-ads-0.3.
|
|
58
|
+
ads_tool_package="/absolute/path/to/reqvire-org-ads-0.3.2.tgz"
|
|
59
59
|
npx --yes --package "$ads_tool_package" ads --help
|
|
60
60
|
npx --yes --package "$ads_tool_package" ads init --framework react --tool-package "$ads_tool_package"
|
|
61
61
|
npm install
|
|
@@ -93,7 +93,8 @@ component rules through the project's plugin adapter and `ds.config.json`, then
|
|
|
93
93
|
run its actual build. Use `lint-config` directly for custom pipeline integration
|
|
94
94
|
or debugging, not as a separate routine setup step.
|
|
95
95
|
|
|
96
|
-
Use ADS 0.3.
|
|
96
|
+
Use ADS 0.3.2 or later for the closed foundation contract and recoverable
|
|
97
|
+
shared-resource preview compiler. `mcp` starts stdio
|
|
97
98
|
plus its local website; `showcase` starts the browser-only MCP runtime. Open the existing runtime URL from `ads_status` when
|
|
98
99
|
connected; otherwise use `npm run dev:showcase` or the installed `ads showcase`.
|
|
99
100
|
The tool client discovers entries and renders MCP Apps. Init adds the launch
|
|
@@ -198,6 +198,25 @@ Use local simulated services in fixtures. Tests may import fixtures; load real
|
|
|
198
198
|
foundation CSS rather than assigning shared token values in JavaScript. Keep
|
|
199
199
|
full-app integration harnesses and workflow coverage in application test support.
|
|
200
200
|
|
|
201
|
+
Foundation CSS is checked, not an escape hatch. Value sheets define shared tokens
|
|
202
|
+
on the document root/themes; `fonts.css` holds font-face metadata. `base.css` has
|
|
203
|
+
three closed roles: root/body typography, canvas and theme defaults; limited native
|
|
204
|
+
normalization; and global focus indicators/reduced-motion rules. Every selector
|
|
205
|
+
branch, property and value must fit its role. Token-backed control skins, layout,
|
|
206
|
+
interactive state rules and owner hooks are rejected even through `:where()`.
|
|
207
|
+
Do not copy an unchecked upstream preflight or turn a component repair into a reset.
|
|
208
|
+
Put skin/state/animation in the Linaria owner, and scenario layout variations in
|
|
209
|
+
the public review pattern's typed API. Browser review still verifies the effects.
|
|
210
|
+
|
|
211
|
+
Invalid metadata and source failures keep the managed compiler alive for repairs.
|
|
212
|
+
Read `ads_status.build` or `ads://project/build`, fix the owner, then wait for current
|
|
213
|
+
successful output. If the builder exited, fix its command and invoke the preview
|
|
214
|
+
again; ADS starts one fresh child without an MCP reconnect. Compiler-code changes
|
|
215
|
+
also reload on the next preview invocation. Reconnect the client when upgrading
|
|
216
|
+
the ADS server package itself. Persistent SSE/notification streams make
|
|
217
|
+
`networkidle` unsuitable: wait for the App's current ready/error state instead.
|
|
218
|
+
|
|
219
|
+
|
|
201
220
|
|
|
202
221
|
|
|
203
222
|
Register component/project Oxlint plugins and rules in `ds.config.json`. Use
|