@hegemonart/get-design-done 1.40.5 → 1.41.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +80 -0
- package/README.md +4 -0
- package/agents/design-auditor.md +5 -6
- package/bin/gdd-detect +20 -0
- package/package.json +5 -1
- package/reference/anti-patterns.md +26 -0
- package/scripts/lib/detect/cli.cjs +111 -0
- package/scripts/lib/detect/engine.cjs +83 -0
- package/scripts/lib/detect/rule-schema.json +31 -0
- package/scripts/lib/detect/rules/ban-01.cjs +33 -0
- package/scripts/lib/detect/rules/ban-02.cjs +33 -0
- package/scripts/lib/detect/rules/ban-03.cjs +33 -0
- package/scripts/lib/detect/rules/ban-05.cjs +33 -0
- package/scripts/lib/detect/rules/ban-06.cjs +33 -0
- package/scripts/lib/detect/rules/ban-07.cjs +33 -0
- package/scripts/lib/detect/rules/ban-08.cjs +33 -0
- package/scripts/lib/detect/rules/ban-09.cjs +33 -0
- package/scripts/lib/detect/rules/ban-11.cjs +33 -0
- package/scripts/lib/detect/rules/ban-12.cjs +33 -0
- package/scripts/lib/detect/rules/ban-13.cjs +33 -0
- package/scripts/lib/detect/rules/index.cjs +21 -0
- package/scripts/lib/manifest/README.md +46 -0
- package/scripts/lib/manifest/harnesses.cjs +3 -0
- package/scripts/lib/manifest/harnesses.json +91 -0
- package/scripts/lib/manifest/index.cjs +26 -0
- package/scripts/lib/manifest/loader.cjs +51 -0
- package/scripts/lib/manifest/prose-denylist.json +126 -0
- package/scripts/lib/manifest/schemas/harnesses.schema.json +38 -0
- package/scripts/lib/manifest/schemas/prose-denylist.schema.json +41 -0
- package/scripts/lib/manifest/schemas/skills.schema.json +33 -0
- package/scripts/lib/manifest/skills.json +255 -0
- package/skills/quality-gate/SKILL.md +1 -1
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// Phase 41 — BAN-05: Pure Black Dark Mode. Ported from reference/anti-patterns.md (its own **Grep**).
|
|
3
|
+
// Pure, dep-free. No `require`. The matcher scans ctx.content; line/column are 1-based.
|
|
4
|
+
|
|
5
|
+
const PATTERN = "background.*#000000|background.*rgb\\(0,\\s*0,\\s*0\\)";
|
|
6
|
+
|
|
7
|
+
/** @param {{content: string, ext: string, path: string}} ctx @returns {{line:number,column:number,match:string}[]} */
|
|
8
|
+
function matcher(ctx) {
|
|
9
|
+
const out = [];
|
|
10
|
+
const re = new RegExp(PATTERN, 'gi');
|
|
11
|
+
const text = String((ctx && ctx.content) || '');
|
|
12
|
+
let m;
|
|
13
|
+
while ((m = re.exec(text)) !== null) {
|
|
14
|
+
const upto = text.slice(0, m.index);
|
|
15
|
+
const line = upto.split('\n').length;
|
|
16
|
+
const lastNl = upto.lastIndexOf('\n');
|
|
17
|
+
const column = lastNl < 0 ? m.index + 1 : m.index - lastNl;
|
|
18
|
+
out.push({ line, column, match: m[0] });
|
|
19
|
+
if (m.index === re.lastIndex) re.lastIndex++; // zero-width guard
|
|
20
|
+
}
|
|
21
|
+
return out;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = {
|
|
25
|
+
id: "BAN-05",
|
|
26
|
+
category: "color",
|
|
27
|
+
name: "Pure Black Dark Mode",
|
|
28
|
+
description: "Pure #000 dark-mode background — harsh contrast + halation; use a near-black surface.",
|
|
29
|
+
references: ["reference/anti-patterns.md#BAN-05"],
|
|
30
|
+
severity: "warn",
|
|
31
|
+
pattern: PATTERN,
|
|
32
|
+
matcher,
|
|
33
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// Phase 41 — BAN-06: Disabling Zoom. Ported from reference/anti-patterns.md (its own **Grep**).
|
|
3
|
+
// Pure, dep-free. No `require`. The matcher scans ctx.content; line/column are 1-based.
|
|
4
|
+
|
|
5
|
+
const PATTERN = "user-scalable=no|maximum-scale=1";
|
|
6
|
+
|
|
7
|
+
/** @param {{content: string, ext: string, path: string}} ctx @returns {{line:number,column:number,match:string}[]} */
|
|
8
|
+
function matcher(ctx) {
|
|
9
|
+
const out = [];
|
|
10
|
+
const re = new RegExp(PATTERN, 'gi');
|
|
11
|
+
const text = String((ctx && ctx.content) || '');
|
|
12
|
+
let m;
|
|
13
|
+
while ((m = re.exec(text)) !== null) {
|
|
14
|
+
const upto = text.slice(0, m.index);
|
|
15
|
+
const line = upto.split('\n').length;
|
|
16
|
+
const lastNl = upto.lastIndexOf('\n');
|
|
17
|
+
const column = lastNl < 0 ? m.index + 1 : m.index - lastNl;
|
|
18
|
+
out.push({ line, column, match: m[0] });
|
|
19
|
+
if (m.index === re.lastIndex) re.lastIndex++; // zero-width guard
|
|
20
|
+
}
|
|
21
|
+
return out;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = {
|
|
25
|
+
id: "BAN-06",
|
|
26
|
+
category: "accessibility",
|
|
27
|
+
name: "Disabling Zoom",
|
|
28
|
+
description: "Viewport meta that disables pinch-zoom — a WCAG 1.4.4 failure.",
|
|
29
|
+
references: ["reference/anti-patterns.md#BAN-06"],
|
|
30
|
+
severity: "error",
|
|
31
|
+
pattern: PATTERN,
|
|
32
|
+
matcher,
|
|
33
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// Phase 41 — BAN-07: Naked outline: none. Ported from reference/anti-patterns.md (its own **Grep**).
|
|
3
|
+
// Pure, dep-free. No `require`. The matcher scans ctx.content; line/column are 1-based.
|
|
4
|
+
|
|
5
|
+
const PATTERN = ":focus\\s*\\{[^}]*outline:\\s*(none|0)";
|
|
6
|
+
|
|
7
|
+
/** @param {{content: string, ext: string, path: string}} ctx @returns {{line:number,column:number,match:string}[]} */
|
|
8
|
+
function matcher(ctx) {
|
|
9
|
+
const out = [];
|
|
10
|
+
const re = new RegExp(PATTERN, 'gi');
|
|
11
|
+
const text = String((ctx && ctx.content) || '');
|
|
12
|
+
let m;
|
|
13
|
+
while ((m = re.exec(text)) !== null) {
|
|
14
|
+
const upto = text.slice(0, m.index);
|
|
15
|
+
const line = upto.split('\n').length;
|
|
16
|
+
const lastNl = upto.lastIndexOf('\n');
|
|
17
|
+
const column = lastNl < 0 ? m.index + 1 : m.index - lastNl;
|
|
18
|
+
out.push({ line, column, match: m[0] });
|
|
19
|
+
if (m.index === re.lastIndex) re.lastIndex++; // zero-width guard
|
|
20
|
+
}
|
|
21
|
+
return out;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = {
|
|
25
|
+
id: "BAN-07",
|
|
26
|
+
category: "accessibility",
|
|
27
|
+
name: "Naked outline: none",
|
|
28
|
+
description: "Removing the focus outline without a replacement — a keyboard-a11y failure.",
|
|
29
|
+
references: ["reference/anti-patterns.md#BAN-07"],
|
|
30
|
+
severity: "error",
|
|
31
|
+
pattern: PATTERN,
|
|
32
|
+
matcher,
|
|
33
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// Phase 41 — BAN-08: transition: all. Ported from reference/anti-patterns.md (its own **Grep**).
|
|
3
|
+
// Pure, dep-free. No `require`. The matcher scans ctx.content; line/column are 1-based.
|
|
4
|
+
|
|
5
|
+
const PATTERN = "transition:\\s*all\\s";
|
|
6
|
+
|
|
7
|
+
/** @param {{content: string, ext: string, path: string}} ctx @returns {{line:number,column:number,match:string}[]} */
|
|
8
|
+
function matcher(ctx) {
|
|
9
|
+
const out = [];
|
|
10
|
+
const re = new RegExp(PATTERN, 'gi');
|
|
11
|
+
const text = String((ctx && ctx.content) || '');
|
|
12
|
+
let m;
|
|
13
|
+
while ((m = re.exec(text)) !== null) {
|
|
14
|
+
const upto = text.slice(0, m.index);
|
|
15
|
+
const line = upto.split('\n').length;
|
|
16
|
+
const lastNl = upto.lastIndexOf('\n');
|
|
17
|
+
const column = lastNl < 0 ? m.index + 1 : m.index - lastNl;
|
|
18
|
+
out.push({ line, column, match: m[0] });
|
|
19
|
+
if (m.index === re.lastIndex) re.lastIndex++; // zero-width guard
|
|
20
|
+
}
|
|
21
|
+
return out;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = {
|
|
25
|
+
id: "BAN-08",
|
|
26
|
+
category: "motion",
|
|
27
|
+
name: "transition: all",
|
|
28
|
+
description: "transition: all animates layout-triggering properties — jank; name the exact properties.",
|
|
29
|
+
references: ["reference/anti-patterns.md#BAN-08"],
|
|
30
|
+
severity: "warn",
|
|
31
|
+
pattern: PATTERN,
|
|
32
|
+
matcher,
|
|
33
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// Phase 41 — BAN-09: scale(0) Animation Entry. Ported from reference/anti-patterns.md (its own **Grep**).
|
|
3
|
+
// Pure, dep-free. No `require`. The matcher scans ctx.content; line/column are 1-based.
|
|
4
|
+
|
|
5
|
+
const PATTERN = "transform:\\s*scale\\(\\s*0\\s*\\)|scale\\(\\s*0\\s*\\)";
|
|
6
|
+
|
|
7
|
+
/** @param {{content: string, ext: string, path: string}} ctx @returns {{line:number,column:number,match:string}[]} */
|
|
8
|
+
function matcher(ctx) {
|
|
9
|
+
const out = [];
|
|
10
|
+
const re = new RegExp(PATTERN, 'gi');
|
|
11
|
+
const text = String((ctx && ctx.content) || '');
|
|
12
|
+
let m;
|
|
13
|
+
while ((m = re.exec(text)) !== null) {
|
|
14
|
+
const upto = text.slice(0, m.index);
|
|
15
|
+
const line = upto.split('\n').length;
|
|
16
|
+
const lastNl = upto.lastIndexOf('\n');
|
|
17
|
+
const column = lastNl < 0 ? m.index + 1 : m.index - lastNl;
|
|
18
|
+
out.push({ line, column, match: m[0] });
|
|
19
|
+
if (m.index === re.lastIndex) re.lastIndex++; // zero-width guard
|
|
20
|
+
}
|
|
21
|
+
return out;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = {
|
|
25
|
+
id: "BAN-09",
|
|
26
|
+
category: "motion",
|
|
27
|
+
name: "scale(0) Animation Entry",
|
|
28
|
+
description: "Entering from scale(0) — nothing materializes from nothing; start at scale(0.95)+opacity.",
|
|
29
|
+
references: ["reference/anti-patterns.md#BAN-09"],
|
|
30
|
+
severity: "warn",
|
|
31
|
+
pattern: PATTERN,
|
|
32
|
+
matcher,
|
|
33
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// Phase 41 — BAN-11: Tinted Image Outline. Ported from reference/anti-patterns.md (its own **Grep**).
|
|
3
|
+
// Pure, dep-free. No `require`. The matcher scans ctx.content; line/column are 1-based.
|
|
4
|
+
|
|
5
|
+
const PATTERN = "outline-(slate|zinc|neutral|gray|stone|blue|red|green|yellow|purple)-\\d+|img\\s*\\{[^}]*outline:\\s*[^}]*#[0-9a-fA-F]{3,8}";
|
|
6
|
+
|
|
7
|
+
/** @param {{content: string, ext: string, path: string}} ctx @returns {{line:number,column:number,match:string}[]} */
|
|
8
|
+
function matcher(ctx) {
|
|
9
|
+
const out = [];
|
|
10
|
+
const re = new RegExp(PATTERN, 'gi');
|
|
11
|
+
const text = String((ctx && ctx.content) || '');
|
|
12
|
+
let m;
|
|
13
|
+
while ((m = re.exec(text)) !== null) {
|
|
14
|
+
const upto = text.slice(0, m.index);
|
|
15
|
+
const line = upto.split('\n').length;
|
|
16
|
+
const lastNl = upto.lastIndexOf('\n');
|
|
17
|
+
const column = lastNl < 0 ? m.index + 1 : m.index - lastNl;
|
|
18
|
+
out.push({ line, column, match: m[0] });
|
|
19
|
+
if (m.index === re.lastIndex) re.lastIndex++; // zero-width guard
|
|
20
|
+
}
|
|
21
|
+
return out;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = {
|
|
25
|
+
id: "BAN-11",
|
|
26
|
+
category: "decoration",
|
|
27
|
+
name: "Tinted Image Outline",
|
|
28
|
+
description: "A colored outline on an image — color contamination; use low-opacity black/white.",
|
|
29
|
+
references: ["reference/anti-patterns.md#BAN-11"],
|
|
30
|
+
severity: "warn",
|
|
31
|
+
pattern: PATTERN,
|
|
32
|
+
matcher,
|
|
33
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// Phase 41 — BAN-12: transition: all (property). Ported from reference/anti-patterns.md (its own **Grep**).
|
|
3
|
+
// Pure, dep-free. No `require`. The matcher scans ctx.content; line/column are 1-based.
|
|
4
|
+
|
|
5
|
+
const PATTERN = "transition:\\s*all|transition-property:\\s*all";
|
|
6
|
+
|
|
7
|
+
/** @param {{content: string, ext: string, path: string}} ctx @returns {{line:number,column:number,match:string}[]} */
|
|
8
|
+
function matcher(ctx) {
|
|
9
|
+
const out = [];
|
|
10
|
+
const re = new RegExp(PATTERN, 'gi');
|
|
11
|
+
const text = String((ctx && ctx.content) || '');
|
|
12
|
+
let m;
|
|
13
|
+
while ((m = re.exec(text)) !== null) {
|
|
14
|
+
const upto = text.slice(0, m.index);
|
|
15
|
+
const line = upto.split('\n').length;
|
|
16
|
+
const lastNl = upto.lastIndexOf('\n');
|
|
17
|
+
const column = lastNl < 0 ? m.index + 1 : m.index - lastNl;
|
|
18
|
+
out.push({ line, column, match: m[0] });
|
|
19
|
+
if (m.index === re.lastIndex) re.lastIndex++; // zero-width guard
|
|
20
|
+
}
|
|
21
|
+
return out;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = {
|
|
25
|
+
id: "BAN-12",
|
|
26
|
+
category: "motion",
|
|
27
|
+
name: "transition: all (property)",
|
|
28
|
+
description: "transition: all / transition-property: all — recalculates layout every transition.",
|
|
29
|
+
references: ["reference/anti-patterns.md#BAN-12"],
|
|
30
|
+
severity: "warn",
|
|
31
|
+
pattern: PATTERN,
|
|
32
|
+
matcher,
|
|
33
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// Phase 41 — BAN-13: will-change: all. Ported from reference/anti-patterns.md (its own **Grep**).
|
|
3
|
+
// Pure, dep-free. No `require`. The matcher scans ctx.content; line/column are 1-based.
|
|
4
|
+
|
|
5
|
+
const PATTERN = "will-change:\\s*all";
|
|
6
|
+
|
|
7
|
+
/** @param {{content: string, ext: string, path: string}} ctx @returns {{line:number,column:number,match:string}[]} */
|
|
8
|
+
function matcher(ctx) {
|
|
9
|
+
const out = [];
|
|
10
|
+
const re = new RegExp(PATTERN, 'gi');
|
|
11
|
+
const text = String((ctx && ctx.content) || '');
|
|
12
|
+
let m;
|
|
13
|
+
while ((m = re.exec(text)) !== null) {
|
|
14
|
+
const upto = text.slice(0, m.index);
|
|
15
|
+
const line = upto.split('\n').length;
|
|
16
|
+
const lastNl = upto.lastIndexOf('\n');
|
|
17
|
+
const column = lastNl < 0 ? m.index + 1 : m.index - lastNl;
|
|
18
|
+
out.push({ line, column, match: m[0] });
|
|
19
|
+
if (m.index === re.lastIndex) re.lastIndex++; // zero-width guard
|
|
20
|
+
}
|
|
21
|
+
return out;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = {
|
|
25
|
+
id: "BAN-13",
|
|
26
|
+
category: "performance",
|
|
27
|
+
name: "will-change: all",
|
|
28
|
+
description: "will-change: all promotes every property to its own GPU layer — huge texture memory.",
|
|
29
|
+
references: ["reference/anti-patterns.md#BAN-13"],
|
|
30
|
+
severity: "warn",
|
|
31
|
+
pattern: PATTERN,
|
|
32
|
+
matcher,
|
|
33
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// Phase 41 — rule registry. Loads every scripts/lib/detect/rules/ban-NN.cjs and exposes the
|
|
3
|
+
// matcher-exempt set (subjective BAN rules with no static matcher: BAN-04 behavior, BAN-10 DOM-depth).
|
|
4
|
+
|
|
5
|
+
const r0 = require('./ban-01.cjs');
|
|
6
|
+
const r1 = require('./ban-02.cjs');
|
|
7
|
+
const r2 = require('./ban-03.cjs');
|
|
8
|
+
const r3 = require('./ban-05.cjs');
|
|
9
|
+
const r4 = require('./ban-06.cjs');
|
|
10
|
+
const r5 = require('./ban-07.cjs');
|
|
11
|
+
const r6 = require('./ban-08.cjs');
|
|
12
|
+
const r7 = require('./ban-09.cjs');
|
|
13
|
+
const r8 = require('./ban-11.cjs');
|
|
14
|
+
const r9 = require('./ban-12.cjs');
|
|
15
|
+
const r10 = require('./ban-13.cjs');
|
|
16
|
+
|
|
17
|
+
const RULES = [r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10];
|
|
18
|
+
// Subjective BAN rules documented in reference/anti-patterns.md but NOT statically detectable.
|
|
19
|
+
const EXEMPT = Object.freeze(['BAN-04', 'BAN-10']);
|
|
20
|
+
|
|
21
|
+
module.exports = { RULES, EXEMPT };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# `scripts/lib/manifest/` — Cross-Phase Source-of-Truth Root
|
|
2
|
+
|
|
3
|
+
Phase 41.5. This directory is the **single source-of-truth root** for roadmap-wide cross-phase
|
|
4
|
+
metadata. Before it existed, Phase 42 / 44 / 45 / 47 each scoped its own "single source" in a different
|
|
5
|
+
corner (`scripts/lib/build/`, `scripts/lib/prose/`, `reference/harness-matrix.json`,
|
|
6
|
+
`scripts/skill-metadata.json`) — four formats, four schemas, four CI drift gates. This is the one root,
|
|
7
|
+
one schema directory, one validator.
|
|
8
|
+
|
|
9
|
+
## Files
|
|
10
|
+
|
|
11
|
+
| File | Owner / writer | Read by |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| `harnesses.json` (+ `harnesses.cjs` view) | Phase 41.5 seed → **42** (build config) + **45** (capability matrix) extend it | the build pipeline, the harness matrix, the multi-harness compiler |
|
|
14
|
+
| `skills.json` | Phase 41.5 seed (live `skills/` names) → **47** enriches (aliases, pin, description budget) | skill-UX tooling |
|
|
15
|
+
| `prose-denylist.json` | Phase 41.5 seed → **43** + **44** | `scripts/lint-prose.cjs` (the editorial gate) |
|
|
16
|
+
| `schemas/*.schema.json` | one JSON Schema per manifest file | `scripts/validate-manifest.cjs` |
|
|
17
|
+
| `loader.cjs` | Phase 41.5 | every consumer |
|
|
18
|
+
| `index.cjs` | Phase 41.5 | every consumer (`readHarnesses` / `readSkills` / `readProseDenylist`) |
|
|
19
|
+
|
|
20
|
+
## Contract
|
|
21
|
+
|
|
22
|
+
- **One canonical record, multiple views.** `harnesses.json` is the canonical harness record; Phase 42's
|
|
23
|
+
build config and Phase 45's capability matrix are *views* of it, not separate files. Consumers extend
|
|
24
|
+
the records with new fields (every schema sets `additionalProperties: true`) — they do not fork the file.
|
|
25
|
+
- **Read through `index.cjs`.** Never `require` a manifest JSON directly. `readHarnesses()` /
|
|
26
|
+
`readSkills()` / `readProseDenylist()` return a well-shaped object even when the file is absent.
|
|
27
|
+
- **Graceful out-of-order shipping.** `loader.cjs` returns an empty manifest + a one-line warning when a
|
|
28
|
+
file is missing or unparseable — a phase that ships before its data exists never crashes. (D-03)
|
|
29
|
+
- **mtime cache.** A manifest is re-read only when its file mtime changes. (D-02)
|
|
30
|
+
- **One CI gate.** `scripts/validate-manifest.cjs` (`npm run validate:manifest`) ajv-validates every
|
|
31
|
+
manifest against its schema. This is the only drift gate — 43/44/45/47 do NOT add their own.
|
|
32
|
+
|
|
33
|
+
## Migration note (for Phase 42 / 43 / 44 / 45 / 47 plan-phases)
|
|
34
|
+
|
|
35
|
+
When you plan your phase, **target this root from day one**. Do not create
|
|
36
|
+
`scripts/lib/build/harness-configs.cjs`, `scripts/lib/prose/denylist.json`,
|
|
37
|
+
`reference/harness-matrix.json`, or `scripts/skill-metadata.json` as new SoTs — extend
|
|
38
|
+
`manifest/harnesses.json` / `manifest/prose-denylist.json` / `manifest/skills.json` instead, read them
|
|
39
|
+
via `index.cjs`, and let `validate-manifest.cjs` be your drift gate. (Phase 46's SoT-consolidation
|
|
40
|
+
paragraph delegates here.)
|
|
41
|
+
|
|
42
|
+
## Boundaries
|
|
43
|
+
|
|
44
|
+
This root holds **structured cross-phase data** (JSON + typed readers). It does not unify with the
|
|
45
|
+
`reference/*.md` prose registries (a different consumer pattern), and it does not auto-generate
|
|
46
|
+
frontmatter (Phase 46 territory).
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": 1,
|
|
3
|
+
"generated_at": null,
|
|
4
|
+
"note": "Canonical cross-phase harness record (Phase 41.5 SoT root). Phase 42 adds build/compile config; Phase 45 adds the capability matrix — both as views of this one record. Model tiers live in reference/runtime-models.md.",
|
|
5
|
+
"harnesses": [
|
|
6
|
+
{
|
|
7
|
+
"id": "claude",
|
|
8
|
+
"name": "Claude Code",
|
|
9
|
+
"config_dir": ".claude",
|
|
10
|
+
"runtime_models_ref": "reference/runtime-models.md#claude"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"id": "codex",
|
|
14
|
+
"name": "OpenAI Codex CLI",
|
|
15
|
+
"config_dir": ".codex",
|
|
16
|
+
"runtime_models_ref": "reference/runtime-models.md#codex"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"id": "gemini",
|
|
20
|
+
"name": "Gemini CLI",
|
|
21
|
+
"config_dir": ".gemini",
|
|
22
|
+
"runtime_models_ref": "reference/runtime-models.md#gemini"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"id": "qwen",
|
|
26
|
+
"name": "Qwen Code",
|
|
27
|
+
"config_dir": ".qwen",
|
|
28
|
+
"runtime_models_ref": "reference/runtime-models.md#qwen"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"id": "kilo",
|
|
32
|
+
"name": "Kilo Code",
|
|
33
|
+
"config_dir": ".kilo",
|
|
34
|
+
"runtime_models_ref": "reference/runtime-models.md#kilo"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"id": "copilot",
|
|
38
|
+
"name": "GitHub Copilot CLI",
|
|
39
|
+
"config_dir": ".copilot",
|
|
40
|
+
"runtime_models_ref": "reference/runtime-models.md#copilot"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"id": "cursor",
|
|
44
|
+
"name": "Cursor",
|
|
45
|
+
"config_dir": ".cursor",
|
|
46
|
+
"runtime_models_ref": "reference/runtime-models.md#cursor"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"id": "windsurf",
|
|
50
|
+
"name": "Windsurf (Cascade)",
|
|
51
|
+
"config_dir": ".windsurf",
|
|
52
|
+
"runtime_models_ref": "reference/runtime-models.md#windsurf"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"id": "antigravity",
|
|
56
|
+
"name": "Antigravity",
|
|
57
|
+
"config_dir": ".antigravity",
|
|
58
|
+
"runtime_models_ref": "reference/runtime-models.md#antigravity"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"id": "augment",
|
|
62
|
+
"name": "Augment",
|
|
63
|
+
"config_dir": ".augment",
|
|
64
|
+
"runtime_models_ref": "reference/runtime-models.md#augment"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"id": "trae",
|
|
68
|
+
"name": "Trae",
|
|
69
|
+
"config_dir": ".trae",
|
|
70
|
+
"runtime_models_ref": "reference/runtime-models.md#trae"
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"id": "codebuddy",
|
|
74
|
+
"name": "CodeBuddy",
|
|
75
|
+
"config_dir": ".codebuddy",
|
|
76
|
+
"runtime_models_ref": "reference/runtime-models.md#codebuddy"
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"id": "cline",
|
|
80
|
+
"name": "Cline",
|
|
81
|
+
"config_dir": ".cline",
|
|
82
|
+
"runtime_models_ref": "reference/runtime-models.md#cline"
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"id": "opencode",
|
|
86
|
+
"name": "OpenCode",
|
|
87
|
+
"config_dir": ".opencode",
|
|
88
|
+
"runtime_models_ref": "reference/runtime-models.md#opencode"
|
|
89
|
+
}
|
|
90
|
+
]
|
|
91
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// Phase 41.5 — manifest/index.cjs — typed readers over the shared loader. Every cross-phase consumer
|
|
3
|
+
// imports from here: `const { readHarnesses } = require('scripts/lib/manifest')`. Each reader returns
|
|
4
|
+
// a well-shaped object even when the underlying file is absent (graceful empty fallback per loader D-03).
|
|
5
|
+
|
|
6
|
+
const loader = require('./loader.cjs');
|
|
7
|
+
|
|
8
|
+
/** @returns {{ schema_version: number, generated_at: string|null, harnesses: object[] }} */
|
|
9
|
+
function readHarnesses(opts) {
|
|
10
|
+
return loader.load('harnesses', { ...opts, fallback: { schema_version: 1, generated_at: null, harnesses: [] } });
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** @returns {{ schema_version: number, skills: object[] }} */
|
|
14
|
+
function readSkills(opts) {
|
|
15
|
+
return loader.load('skills', { ...opts, fallback: { schema_version: 1, skills: [] } });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** @returns {{ schema_version: number, tells: object[] }} */
|
|
19
|
+
function readProseDenylist(opts) {
|
|
20
|
+
return loader.load('prose-denylist', { ...opts, fallback: { schema_version: 1, tells: [] } });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
module.exports = {
|
|
24
|
+
readHarnesses, readSkills, readProseDenylist,
|
|
25
|
+
reset: loader.reset, MANIFEST_DIR: loader.MANIFEST_DIR,
|
|
26
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// Phase 41.5 — manifest/loader.cjs — the ONE shared reader for every cross-phase SoT manifest under
|
|
3
|
+
// scripts/lib/manifest/. Phases 42 (harnesses), 43/44 (prose denylist), 45 (capability matrix), and
|
|
4
|
+
// 47 (skill metadata) all read through here instead of hand-rolling their own loader + drift gate.
|
|
5
|
+
//
|
|
6
|
+
// Graceful (D-03): a missing or unparseable manifest returns the caller's `fallback` (an empty
|
|
7
|
+
// manifest) plus a one-line stderr warning — NEVER a throw — so a phase shipping before its data file
|
|
8
|
+
// exists does not crash. File-mtime cache (D-02): a file is re-read only when its mtime changes.
|
|
9
|
+
//
|
|
10
|
+
// Dep-free (no ajv here — validation lives in scripts/validate-manifest.cjs, the CI gate). No require
|
|
11
|
+
// of any third-party module.
|
|
12
|
+
|
|
13
|
+
const fs = require('node:fs');
|
|
14
|
+
const path = require('node:path');
|
|
15
|
+
|
|
16
|
+
const MANIFEST_DIR = __dirname;
|
|
17
|
+
const _cache = new Map(); // absPath -> { mtimeMs, data }
|
|
18
|
+
|
|
19
|
+
/** Clear the in-process cache (tests). */
|
|
20
|
+
function reset() { _cache.clear(); }
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Load a manifest JSON by base name (no extension).
|
|
24
|
+
* @param {string} name e.g. 'harnesses' | 'skills' | 'prose-denylist'
|
|
25
|
+
* @param {{ dir?: string, fallback?: any, quiet?: boolean }} [opts]
|
|
26
|
+
* @returns the parsed manifest, or `fallback` (default {}) on missing/parse-error.
|
|
27
|
+
*/
|
|
28
|
+
function load(name, opts) {
|
|
29
|
+
const o = opts || {};
|
|
30
|
+
const dir = o.dir || MANIFEST_DIR;
|
|
31
|
+
const fallback = Object.prototype.hasOwnProperty.call(o, 'fallback') ? o.fallback : {};
|
|
32
|
+
const abs = path.join(dir, `${name}.json`);
|
|
33
|
+
|
|
34
|
+
let stat;
|
|
35
|
+
try { stat = fs.statSync(abs); } catch {
|
|
36
|
+
if (!o.quiet) process.stderr.write(`manifest: ${name}.json not found — using empty fallback (a consumer phase may not have shipped its data yet)\n`);
|
|
37
|
+
return fallback;
|
|
38
|
+
}
|
|
39
|
+
const cached = _cache.get(abs);
|
|
40
|
+
if (cached && cached.mtimeMs === stat.mtimeMs) return cached.data;
|
|
41
|
+
try {
|
|
42
|
+
const data = JSON.parse(fs.readFileSync(abs, 'utf8'));
|
|
43
|
+
_cache.set(abs, { mtimeMs: stat.mtimeMs, data });
|
|
44
|
+
return data;
|
|
45
|
+
} catch (e) {
|
|
46
|
+
if (!o.quiet) process.stderr.write(`manifest: ${name}.json parse error (${e.message}) — using empty fallback\n`);
|
|
47
|
+
return fallback;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
module.exports = { load, reset, MANIFEST_DIR };
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": 1,
|
|
3
|
+
"note": "AI-tell denylist SoT (Phase 41.5 seed). Phase 43 (lint-prose.cjs) + Phase 44 read this. Case-insensitive phrase match in the project's OWN user-facing prose only.",
|
|
4
|
+
"tells": [
|
|
5
|
+
{
|
|
6
|
+
"pattern": "load-bearing",
|
|
7
|
+
"kind": "phrase",
|
|
8
|
+
"note": "AI-prose tell (training-set monoculture)."
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"pattern": "highest-leverage",
|
|
12
|
+
"kind": "phrase",
|
|
13
|
+
"note": "AI-prose tell (training-set monoculture)."
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"pattern": "delve",
|
|
17
|
+
"kind": "phrase",
|
|
18
|
+
"note": "AI-prose tell (training-set monoculture)."
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"pattern": "delves",
|
|
22
|
+
"kind": "phrase",
|
|
23
|
+
"note": "AI-prose tell (training-set monoculture)."
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"pattern": "seamless",
|
|
27
|
+
"kind": "phrase",
|
|
28
|
+
"note": "AI-prose tell (training-set monoculture)."
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"pattern": "seamlessly",
|
|
32
|
+
"kind": "phrase",
|
|
33
|
+
"note": "AI-prose tell (training-set monoculture)."
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"pattern": "robust",
|
|
37
|
+
"kind": "phrase",
|
|
38
|
+
"note": "AI-prose tell (training-set monoculture)."
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"pattern": "elevate",
|
|
42
|
+
"kind": "phrase",
|
|
43
|
+
"note": "AI-prose tell (training-set monoculture)."
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"pattern": "empower",
|
|
47
|
+
"kind": "phrase",
|
|
48
|
+
"note": "AI-prose tell (training-set monoculture)."
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"pattern": "underscore",
|
|
52
|
+
"kind": "phrase",
|
|
53
|
+
"note": "AI-prose tell (training-set monoculture)."
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"pattern": "underscores",
|
|
57
|
+
"kind": "phrase",
|
|
58
|
+
"note": "AI-prose tell (training-set monoculture)."
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"pattern": "in today's",
|
|
62
|
+
"kind": "phrase",
|
|
63
|
+
"note": "AI-prose tell (training-set monoculture)."
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"pattern": "let's dive in",
|
|
67
|
+
"kind": "phrase",
|
|
68
|
+
"note": "AI-prose tell (training-set monoculture)."
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"pattern": "moreover",
|
|
72
|
+
"kind": "phrase",
|
|
73
|
+
"note": "AI-prose tell (training-set monoculture)."
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"pattern": "furthermore",
|
|
77
|
+
"kind": "phrase",
|
|
78
|
+
"note": "AI-prose tell (training-set monoculture)."
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"pattern": "tapestry",
|
|
82
|
+
"kind": "phrase",
|
|
83
|
+
"note": "AI-prose tell (training-set monoculture)."
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"pattern": "a testament to",
|
|
87
|
+
"kind": "phrase",
|
|
88
|
+
"note": "AI-prose tell (training-set monoculture)."
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"pattern": "navigating the",
|
|
92
|
+
"kind": "phrase",
|
|
93
|
+
"note": "AI-prose tell (training-set monoculture)."
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"pattern": "in the realm of",
|
|
97
|
+
"kind": "phrase",
|
|
98
|
+
"note": "AI-prose tell (training-set monoculture)."
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"pattern": "unlock the power",
|
|
102
|
+
"kind": "phrase",
|
|
103
|
+
"note": "AI-prose tell (training-set monoculture)."
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"pattern": "game-changer",
|
|
107
|
+
"kind": "phrase",
|
|
108
|
+
"note": "AI-prose tell (training-set monoculture)."
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"pattern": "leverage",
|
|
112
|
+
"kind": "phrase",
|
|
113
|
+
"note": "AI-prose tell (training-set monoculture)."
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"pattern": "\\u2014",
|
|
117
|
+
"kind": "token",
|
|
118
|
+
"note": "Em dash — banned in the project's own user-facing prose (Phase 43)."
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"pattern": "--",
|
|
122
|
+
"kind": "token",
|
|
123
|
+
"note": "Double hyphen (often an em-dash surrogate)."
|
|
124
|
+
}
|
|
125
|
+
]
|
|
126
|
+
}
|