synthesisui 0.9.0 → 0.9.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/commands/doctor.js +61 -23
- package/dist/doctor/self-conflict.js +37 -0
- package/package.json +1 -1
package/dist/commands/doctor.js
CHANGED
|
@@ -2,7 +2,7 @@ import { readdir, readFile } from "node:fs/promises";
|
|
|
2
2
|
import { join, relative, resolve } from "node:path";
|
|
3
3
|
import { bindingsFromDocument, countComponents, findOverrides, } from "../doctor/overrides.js";
|
|
4
4
|
import { diagnose, scanSource, } from "../doctor/scan.js";
|
|
5
|
-
import { findSelfConflicts } from "../doctor/self-conflict.js";
|
|
5
|
+
import { findSelfConflicts, forbiddenProps, isReset, propMatchesLabel, } from "../doctor/self-conflict.js";
|
|
6
6
|
import { buildTable, EMPTY_TABLE, nearestToken, } from "../doctor/tokens.js";
|
|
7
7
|
import { body, section, snippet } from "../output.js";
|
|
8
8
|
/**
|
|
@@ -313,10 +313,64 @@ export async function doctor(opts) {
|
|
|
313
313
|
console.log(body(`+${files.length - shownFiles.length} more files. Run with --all to see everything.`));
|
|
314
314
|
}
|
|
315
315
|
}
|
|
316
|
+
// Every other section asks whether the code obeys the system. This one asks
|
|
317
|
+
// whether the system obeys itself, and it is only here because the tool once
|
|
318
|
+
// reported a consumer for overruling a recipe while they were obeying the
|
|
319
|
+
// law written on that same component.
|
|
320
|
+
const conflicts = documents.flatMap((doc) => findSelfConflicts(doc));
|
|
321
|
+
const conflictsInUse = conflicts.filter((c) => used.has(c.component));
|
|
322
|
+
if (conflictsInUse.length > 0) {
|
|
323
|
+
console.log(section("Your system contradicts itself"));
|
|
324
|
+
console.log(body(conflictsInUse.length === 1
|
|
325
|
+
? "One component says one thing in prose and another in its recipe."
|
|
326
|
+
: `${conflictsInUse.length} components say one thing in prose and another in their recipe.`));
|
|
327
|
+
console.log("");
|
|
328
|
+
for (const c of conflictsInUse) {
|
|
329
|
+
console.log(body(`ds-${c.component}`));
|
|
330
|
+
console.log(` "${c.law}"`);
|
|
331
|
+
console.log(` but ${c.where} binds ${c.value}`);
|
|
332
|
+
console.log("");
|
|
333
|
+
}
|
|
334
|
+
console.log(body("Whoever wrote the law and whoever wrote the recipe disagree."));
|
|
335
|
+
console.log(body("Until they do not, no code here can be correct."));
|
|
336
|
+
}
|
|
316
337
|
if (overrides.length > 0) {
|
|
338
|
+
// An override on a property the component's own law FORBIDS, written as a
|
|
339
|
+
// reset, is not drift - it is the author keeping a promise the recipe
|
|
340
|
+
// broke. Counting it against them was the report being unfair to the only
|
|
341
|
+
// person in the loop who read the law (investidorez, 25/07).
|
|
342
|
+
const forbidden = new Map();
|
|
343
|
+
for (const doc of documents) {
|
|
344
|
+
for (const [comp, props] of forbiddenProps(doc)) {
|
|
345
|
+
const merged = forbidden.get(comp) ?? new Map();
|
|
346
|
+
for (const [label, law] of props)
|
|
347
|
+
merged.set(label, law);
|
|
348
|
+
forbidden.set(comp, merged);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
const lawKept = (o) => {
|
|
352
|
+
if (!isReset(o.wrote))
|
|
353
|
+
return null;
|
|
354
|
+
const props = forbidden.get(o.component);
|
|
355
|
+
if (!props)
|
|
356
|
+
return null;
|
|
357
|
+
for (const [label, law] of props) {
|
|
358
|
+
if (propMatchesLabel(o.prop, label))
|
|
359
|
+
return law;
|
|
360
|
+
}
|
|
361
|
+
return null;
|
|
362
|
+
};
|
|
363
|
+
const drifting = overrides.filter((o) => lawKept(o) === null);
|
|
364
|
+
const correcting = overrides.length - drifting.length;
|
|
317
365
|
console.log(section("Overruled"));
|
|
318
|
-
console.log(body(`${
|
|
366
|
+
console.log(body(`${drifting.length} place${drifting.length === 1 ? "" : "s"} where the code takes a component`));
|
|
319
367
|
console.log(body("the system defines, and then overrules it locally."));
|
|
368
|
+
if (correcting > 0) {
|
|
369
|
+
console.log("");
|
|
370
|
+
console.log(body(correcting === 1
|
|
371
|
+
? "One more overrules it to KEEP a law the recipe breaks - marked below."
|
|
372
|
+
: `${correcting} more overrule it to KEEP a law the recipe breaks - marked below.`));
|
|
373
|
+
}
|
|
320
374
|
console.log("");
|
|
321
375
|
const byFile = new Map();
|
|
322
376
|
for (const o of overrides) {
|
|
@@ -338,6 +392,11 @@ export async function doctor(opts) {
|
|
|
338
392
|
console.log(` the recipe binds ${o.recipe}`);
|
|
339
393
|
else if (o.where)
|
|
340
394
|
console.log(` the recipe binds it ${o.where}`);
|
|
395
|
+
const kept = lawKept(o);
|
|
396
|
+
if (kept) {
|
|
397
|
+
console.log(` ✓ but the law says: "${kept}"`);
|
|
398
|
+
console.log(" this override keeps it - the recipe does not");
|
|
399
|
+
}
|
|
341
400
|
}
|
|
342
401
|
if (!opts.all && list.length > 3) {
|
|
343
402
|
console.log(` +${list.length - 3} more`);
|
|
@@ -351,27 +410,6 @@ export async function doctor(opts) {
|
|
|
351
410
|
// The system's own words, for the components this project actually uses.
|
|
352
411
|
// Prose, so nothing verifies it - the value is putting it in front of
|
|
353
412
|
// whoever is touching the component, which no other tool is positioned to
|
|
354
|
-
// Every other section asks whether the code obeys the system. This one asks
|
|
355
|
-
// whether the system obeys itself, and it is only here because the tool once
|
|
356
|
-
// reported a consumer for overruling a recipe while they were obeying the
|
|
357
|
-
// law written on that same component.
|
|
358
|
-
const conflicts = documents.flatMap((doc) => findSelfConflicts(doc));
|
|
359
|
-
const conflictsInUse = conflicts.filter((c) => used.has(c.component));
|
|
360
|
-
if (conflictsInUse.length > 0) {
|
|
361
|
-
console.log(section("Your system contradicts itself"));
|
|
362
|
-
console.log(body(conflictsInUse.length === 1
|
|
363
|
-
? "One component says one thing in prose and another in its recipe."
|
|
364
|
-
: `${conflictsInUse.length} components say one thing in prose and another in their recipe.`));
|
|
365
|
-
console.log("");
|
|
366
|
-
for (const c of conflictsInUse) {
|
|
367
|
-
console.log(body(`ds-${c.component}`));
|
|
368
|
-
console.log(` "${c.law}"`);
|
|
369
|
-
console.log(` but ${c.where} binds ${c.value}`);
|
|
370
|
-
console.log("");
|
|
371
|
-
}
|
|
372
|
-
console.log(body("Whoever wrote the law and whoever wrote the recipe disagree."));
|
|
373
|
-
console.log(body("Until they do not, no code here can be correct."));
|
|
374
|
-
}
|
|
375
413
|
// do because no other tool knows these laws exist.
|
|
376
414
|
const inUse = [...used.entries()]
|
|
377
415
|
.filter(([name]) => (recipes.get(name)?.usage.length ?? 0) > 0)
|
|
@@ -94,6 +94,43 @@ function* declarations(node, where) {
|
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* Per component, the properties its own laws forbid and the law that does it.
|
|
99
|
+
*
|
|
100
|
+
* The report needs this separately from the conflicts: an override on a
|
|
101
|
+
* forbidden property is not drift, it is the author keeping a promise the
|
|
102
|
+
* recipe broke. Listing them together was accusing the person who was right.
|
|
103
|
+
*/
|
|
104
|
+
export function forbiddenProps(document) {
|
|
105
|
+
const out = new Map();
|
|
106
|
+
const doc = (document ?? {});
|
|
107
|
+
const components = (doc.components ?? {});
|
|
108
|
+
for (const [name, raw] of Object.entries(components)) {
|
|
109
|
+
const recipe = (raw ?? {});
|
|
110
|
+
const laws = Array.isArray(recipe.usage)
|
|
111
|
+
? recipe.usage.filter((x) => typeof x === "string")
|
|
112
|
+
: [];
|
|
113
|
+
for (const law of laws) {
|
|
114
|
+
for (const rule of FORBIDDABLE) {
|
|
115
|
+
if (!forbids(law, rule.words))
|
|
116
|
+
continue;
|
|
117
|
+
const byProp = out.get(name) ?? new Map();
|
|
118
|
+
byProp.set(rule.label, law);
|
|
119
|
+
out.set(name, byProp);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return out;
|
|
124
|
+
}
|
|
125
|
+
/** Is this property one the law names, given how the rule matches? */
|
|
126
|
+
export function propMatchesLabel(prop, label) {
|
|
127
|
+
const rule = FORBIDDABLE.find((r) => r.label === label);
|
|
128
|
+
return rule ? rule.prop.test(prop) : false;
|
|
129
|
+
}
|
|
130
|
+
/** A value that removes rather than sets - the law being kept. */
|
|
131
|
+
export function isReset(value) {
|
|
132
|
+
return !isSet(value);
|
|
133
|
+
}
|
|
97
134
|
/**
|
|
98
135
|
* Every place a component's recipe does what its own law forbids.
|
|
99
136
|
*/
|
package/package.json
CHANGED