@dittowords/spec-cli 0.0.1-alpha.1 → 0.0.1-alpha.10
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/README.md +94 -26
- package/dist/api.d.ts +81 -0
- package/dist/api.js +101 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +4 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +86 -0
- package/dist/commands/check.d.ts +1 -0
- package/dist/commands/check.js +57 -0
- package/dist/commands/create-rule.d.ts +8 -0
- package/dist/commands/create-rule.js +33 -0
- package/dist/commands/init.d.ts +6 -0
- package/dist/commands/init.js +237 -0
- package/dist/commands/list.d.ts +1 -0
- package/dist/commands/list.js +51 -0
- package/dist/commands/pull.d.ts +5 -0
- package/dist/commands/pull.js +168 -0
- package/dist/commands/scaffold.d.ts +6 -0
- package/dist/commands/scaffold.js +51 -0
- package/dist/config.d.ts +16 -0
- package/dist/config.js +90 -0
- package/dist/discover.d.ts +5 -0
- package/dist/discover.js +24 -0
- package/dist/parse.d.ts +24 -0
- package/dist/parse.js +42 -0
- package/dist/serialize.d.ts +5 -0
- package/dist/serialize.js +60 -0
- package/dist/skill-content.d.ts +1 -0
- package/dist/skill-content.js +156 -0
- package/dist/skills.d.ts +4 -0
- package/dist/skills.js +278 -0
- package/package.json +19 -5
- package/src/api.ts +0 -45
- package/src/bin.js +0 -3
- package/src/cli.ts +0 -70
- package/src/commands/check.ts +0 -48
- package/src/commands/init.ts +0 -219
- package/src/commands/list.ts +0 -59
- package/src/commands/pull.ts +0 -140
- package/src/commands/scaffold.ts +0 -40
- package/src/config.ts +0 -55
- package/src/discover.ts +0 -23
- package/src/parse.ts +0 -45
- package/src/serialize.ts +0 -38
package/dist/parse.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface SurfaceLike {
|
|
2
|
+
tags?: string[];
|
|
3
|
+
maxLength?: number;
|
|
4
|
+
}
|
|
5
|
+
export interface SpecLike {
|
|
6
|
+
tags?: string[];
|
|
7
|
+
surfaces?: Record<string, SurfaceLike>;
|
|
8
|
+
}
|
|
9
|
+
export interface ParsedSpec {
|
|
10
|
+
kind: "component" | "workspace";
|
|
11
|
+
name?: string;
|
|
12
|
+
spec: Record<string, unknown>;
|
|
13
|
+
filePath: string;
|
|
14
|
+
}
|
|
15
|
+
export declare class ParseError extends Error {
|
|
16
|
+
filePath: string;
|
|
17
|
+
reason: string;
|
|
18
|
+
constructor(filePath: string, reason: string);
|
|
19
|
+
}
|
|
20
|
+
export declare function parseSpecFile(filePath: string): ParsedSpec;
|
|
21
|
+
export declare function extractFrontmatter(source: string, filePath: string): {
|
|
22
|
+
yaml: string;
|
|
23
|
+
afterFrontmatter: string;
|
|
24
|
+
};
|
package/dist/parse.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ParseError = void 0;
|
|
7
|
+
exports.parseSpecFile = parseSpecFile;
|
|
8
|
+
exports.extractFrontmatter = extractFrontmatter;
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
const js_yaml_1 = __importDefault(require("js-yaml"));
|
|
11
|
+
class ParseError extends Error {
|
|
12
|
+
constructor(filePath, reason) {
|
|
13
|
+
super(`${filePath}: ${reason}`);
|
|
14
|
+
this.filePath = filePath;
|
|
15
|
+
this.reason = reason;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
exports.ParseError = ParseError;
|
|
19
|
+
function parseSpecFile(filePath) {
|
|
20
|
+
const source = fs_1.default.readFileSync(filePath, "utf8");
|
|
21
|
+
const { yaml: frontmatter } = extractFrontmatter(source, filePath);
|
|
22
|
+
const raw = js_yaml_1.default.load(frontmatter);
|
|
23
|
+
if (!raw || typeof raw !== "object") {
|
|
24
|
+
throw new ParseError(filePath, "frontmatter must be a YAML object");
|
|
25
|
+
}
|
|
26
|
+
const spec = raw;
|
|
27
|
+
if (spec.workspace === true) {
|
|
28
|
+
return { kind: "workspace", spec, filePath };
|
|
29
|
+
}
|
|
30
|
+
if (typeof spec.component !== "string" || !spec.component) {
|
|
31
|
+
throw new ParseError(filePath, "missing required 'component' key (string)");
|
|
32
|
+
}
|
|
33
|
+
return { kind: "component", name: spec.component, spec, filePath };
|
|
34
|
+
}
|
|
35
|
+
function extractFrontmatter(source, filePath) {
|
|
36
|
+
const cleaned = source.replace(/^\uFEFF/, "");
|
|
37
|
+
const match = cleaned.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
38
|
+
if (!match) {
|
|
39
|
+
throw new ParseError(filePath, "no YAML frontmatter found (expected --- delimiters)");
|
|
40
|
+
}
|
|
41
|
+
return { yaml: match[1], afterFrontmatter: cleaned.slice(match[0].length) };
|
|
42
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.rewriteManagedKeys = rewriteManagedKeys;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const js_yaml_1 = __importDefault(require("js-yaml"));
|
|
9
|
+
const parse_1 = require("./parse");
|
|
10
|
+
const MANAGED_COMMENT = "# Managed by Ditto — do not edit below";
|
|
11
|
+
function rewriteManagedKeys(filePath, updates) {
|
|
12
|
+
const source = fs_1.default.readFileSync(filePath, "utf8");
|
|
13
|
+
const { yaml: frontmatterYaml, afterFrontmatter } = (0, parse_1.extractFrontmatter)(source, filePath);
|
|
14
|
+
const spec = js_yaml_1.default.load(frontmatterYaml);
|
|
15
|
+
if (updates.tags !== undefined) {
|
|
16
|
+
const savedRules = spec.rules;
|
|
17
|
+
const savedLocales = spec.locales;
|
|
18
|
+
delete spec.rules;
|
|
19
|
+
delete spec.locales;
|
|
20
|
+
spec.tags = updates.tags;
|
|
21
|
+
spec.rules = savedRules;
|
|
22
|
+
if (savedLocales !== undefined)
|
|
23
|
+
spec.locales = savedLocales;
|
|
24
|
+
}
|
|
25
|
+
if (updates.rules !== undefined) {
|
|
26
|
+
const savedLocales = spec.locales;
|
|
27
|
+
delete spec.locales;
|
|
28
|
+
spec.rules = updates.rules;
|
|
29
|
+
if (savedLocales !== undefined)
|
|
30
|
+
spec.locales = savedLocales;
|
|
31
|
+
}
|
|
32
|
+
if (updates.locales !== undefined) {
|
|
33
|
+
if (Object.keys(updates.locales).length > 0) {
|
|
34
|
+
spec.locales = updates.locales;
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
delete spec.locales;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
let dumped = js_yaml_1.default
|
|
41
|
+
.dump(spec, {
|
|
42
|
+
lineWidth: -1,
|
|
43
|
+
noRefs: true,
|
|
44
|
+
sortKeys: false,
|
|
45
|
+
quotingType: '"',
|
|
46
|
+
})
|
|
47
|
+
.trimEnd();
|
|
48
|
+
dumped = insertManagedComment(dumped, updates.tags !== undefined);
|
|
49
|
+
fs_1.default.writeFileSync(filePath, `---\n${dumped}\n---${afterFrontmatter}`);
|
|
50
|
+
}
|
|
51
|
+
function insertManagedComment(dumped, managedTagsPresent) {
|
|
52
|
+
const targetKey = managedTagsPresent ? "tags" : "rules";
|
|
53
|
+
const idx = dumped.search(new RegExp(`^${targetKey}:`, "m"));
|
|
54
|
+
if (idx === -1)
|
|
55
|
+
return dumped;
|
|
56
|
+
const before = dumped.slice(0, idx);
|
|
57
|
+
if (before.includes(MANAGED_COMMENT))
|
|
58
|
+
return dumped;
|
|
59
|
+
return before + MANAGED_COMMENT + "\n" + dumped.slice(idx);
|
|
60
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const SPEC_COMPONENT_SKILL = "---\ndescription: Analyze a component, create ditto specs for it and its dependencies, identify rule gaps, and audit copy\nargument-hint: <ComponentName or path/to/component>\nallowed-tools: [Read, Bash, Edit, Write, Grep, Glob, Agent]\n---\n\n# Spec Component\n\nAnalyze a design system component, scaffold `.ditto.md` content specs (including child components that lack specs), auto-fill surfaces and tags, identify style guide rule gaps from real code usage, create new rules on the platform, and audit copy compliance.\n\n## Input\n\n$ARGUMENTS\n\nAccept either a component name (e.g. `Button`) or a file path (e.g. `src/components/Button/index.tsx`). If a name is given, search the codebase to find the component. If multiple matches exist, ask the user to clarify.\n\n---\n\n## Phase 1: Discovery\n\nFind the target component and map its dependencies.\n\n1. Locate the component source file.\n2. Read the component code. Walk imports to find **child components that render user-facing text** (components that accept string props, children rendered as text, or contain hardcoded strings). Skip utility imports, style imports, icon imports, and non-text components.\n3. Check which components already have an `index.ditto.md` spec by looking for the file in each component's directory.\n4. Read `workspace.ditto.md` at the project root (if it exists) for existing platform tags and universal rules.\n5. Report:\n - Target component and its location\n - Child components needing specs (no existing `index.ditto.md`)\n - Existing specs found (already covered)\n\n---\n\n## Phase 2: Surface Analysis\n\nFor each component that needs a new spec (target + unspecced children):\n\n1. Read the TypeScript interface / props type definition.\n2. Identify every **text surface** \u2014 each piece of user-facing copy the component renders:\n - **String props** rendered in JSX \u2192 use the prop name as the surface key\n - **`children`** when used as text \u2192 use `$children` as the surface key\n - **Nested props** \u2192 use dot notation (e.g. `primaryAction.label`)\n - **Hardcoded strings** in JSX \u2192 use a descriptive role name (e.g. `headline`, `submitLabel`, `bodyText`)\n3. For each surface, suggest **tags** from the `workspace.ditto.md` tag inventory, matched by semantic role (e.g. `heading`, `body`, `button`, `call-to-action`). Prefer reusing existing tags. Only propose a new tag if nothing fits \u2014 and note it will be new.\n4. Estimate **`maxLength`** where layout context provides constraints (e.g. single-line headings, button widths). Omit if no clear constraint.\n\nPresent the proposed spec for each component in exact YAML format:\n\n```yaml\n---\ncomponent: ComponentName\ntags: [relevant-tags]\nsurfaces:\n surfaceKey:\n tags: [semantic-tags]\n maxLength: 60\n# Managed by Ditto \u2014 do not edit below\nrules: []\n---\n```\n\n**STOP HERE. Ask the user to review and approve the proposed surfaces before creating any files.** Let them add, remove, or modify surfaces and tags.\n\n---\n\n## Phase 3: Create Specs\n\nAfter user approval:\n\n1. For each component, run:\n ```\n npx ditto-spec scaffold <ComponentName> --path <component-directory>\n ```\n2. Edit each created `index.ditto.md` to replace the empty `surfaces: {}` and `tags: []` with the approved values. Only edit developer-owned keys (`component`, `tags`, `surfaces`). Never write `rules` by hand.\n3. Run `npx ditto-spec pull` to populate the `rules` section from the platform.\n4. Run `npx ditto-spec check` to validate all spec files.\n\nReport which specs were created and how many rules were pulled.\n\n---\n\n## Phase 4: Rule Gap Analysis\n\nIdentify copy patterns that should be rules but aren't.\n\n1. Read all rules now in the spec files (workspace-level from `workspace.ditto.md` + component-level and per-surface from each `index.ditto.md`).\n2. Search the codebase for **instances where the target component is used** \u2014 find all files that import and render it.\n3. For each usage instance, read the **actual copy** being passed to the component's text surfaces (string literals, template strings, variables with obvious values).\n4. Analyze the copy across instances for patterns no existing rule covers:\n - Inconsistent terminology across instances\n - Tone mismatches (e.g. some formal, some casual)\n - Anti-patterns: passive voice in CTAs, overly long descriptions, redundant words\n - Surface-specific conventions that should be formalized (e.g. \"all action buttons start with a verb\")\n5. If **no meaningful gaps** are found, say so and skip to Phase 6.\n6. If gaps are found, propose new rules. For each:\n - **name**: short rule name\n - **description**: what the rule enforces\n - **tags**: which tags it should apply to (determines which surfaces it matches)\n - **examples**: `{from, to}` pairs drawn from actual code showing the pattern\n\nIf any proposed tags don't exist in the workspace yet, warn the user. Currently, only existing workspace tags can be assigned to rules. New tags may need to be created on the platform first.\n\n**STOP HERE. Present proposals and ask the user which rules (if any) to create.**\n\n---\n\n## Phase 5: Create Rules and Re-Pull\n\nFor each approved rule:\n\n1. Run:\n ```\n npx ditto-spec create-rule --name \"<rule name>\" --description \"<description>\" --tags \"<tag1,tag2>\" --examples '<json array of {from,to}>'\n ```\n If a rule uses tags that don't exist yet, warn the user and omit those tags from the command (the API rejects unknown tags).\n\n2. After all rules are created, run `npx ditto-spec pull` to sync the new rules into spec files.\n\n3. Verify the specs now contain the expected rules by reading the updated `index.ditto.md` files.\n\n---\n\n## Phase 6: Copy Audit\n\nEvaluate all component instances against the full rule set.\n\n1. Re-read the fully-populated spec files (workspace + component + surface rules).\n2. Find all instances of the component in the codebase.\n3. For each instance, evaluate the actual copy against every applicable rule:\n - Workspace rules apply to all surfaces\n - Component-level rules (no `surface` field) apply to all surfaces\n - Per-surface rules (with `surface` field) apply only to that surface\n - Respect `maxLength` constraints \u2014 flag any text that exceeds the limit\n4. Report violations:\n - File and line where the instance appears\n - The text that violates the rule\n - Which rule is violated\n - Suggested corrected copy\n\n---\n\n## Reference: Ditto Spec Conventions\n\nThese conventions are essential for creating correct specs:\n\n- **Surface keys**: prop name for props, `$children` for children text, dot-notation for nested props (`primaryAction.label`), descriptive role for hardcoded strings (`headline`, `bodyText`).\n- **Tags**: lowercase, hyphenated. Match semantic role (e.g. `heading`, `body`, `button`, `call-to-action`, `dialog-title`). Check `workspace.ditto.md` `tags` key for the full inventory.\n- **Developer-owned keys**: `component`, `tags`, `surfaces` \u2014 edit freely.\n- **CLI-managed keys**: `rules` (and workspace `tags`) \u2014 **never write by hand**. Populated by `ditto-spec pull`.\n- **Three-level rule hierarchy**: workspace rules (apply everywhere) \u2192 component-level rules (no `surface` field, apply to all surfaces in that component) \u2192 per-surface rules (with `surface: \"<key>\"`, apply only to that surface).\n- **Parent-child layering**: if a parent component passes text to a child (e.g. a label to a Button), the parent declares a surface in its own spec. Both parent and child rules apply \u2014 the parent's rules (e.g. dialog-level tone) layer with the child's rules (e.g. button-level constraints). A child having its own spec does NOT exempt the parent from declaring surfaces for text it provides.\n- **Tag matching**: rules match specs by tag intersection. Any shared tag triggers the match. If a rule matches both component-level and surface-level tags, it emits at component level (broader scope wins).\n";
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SPEC_COMPONENT_SKILL = void 0;
|
|
4
|
+
exports.SPEC_COMPONENT_SKILL = `---
|
|
5
|
+
description: Analyze a component, create ditto specs for it and its dependencies, identify rule gaps, and audit copy
|
|
6
|
+
argument-hint: <ComponentName or path/to/component>
|
|
7
|
+
allowed-tools: [Read, Bash, Edit, Write, Grep, Glob, Agent]
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Spec Component
|
|
11
|
+
|
|
12
|
+
Analyze a design system component, scaffold \`.ditto.md\` content specs (including child components that lack specs), auto-fill surfaces and tags, identify style guide rule gaps from real code usage, create new rules on the platform, and audit copy compliance.
|
|
13
|
+
|
|
14
|
+
## Input
|
|
15
|
+
|
|
16
|
+
$ARGUMENTS
|
|
17
|
+
|
|
18
|
+
Accept either a component name (e.g. \`Button\`) or a file path (e.g. \`src/components/Button/index.tsx\`). If a name is given, search the codebase to find the component. If multiple matches exist, ask the user to clarify.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Phase 1: Discovery
|
|
23
|
+
|
|
24
|
+
Find the target component and map its dependencies.
|
|
25
|
+
|
|
26
|
+
1. Locate the component source file.
|
|
27
|
+
2. Read the component code. Walk imports to find **child components that render user-facing text** (components that accept string props, children rendered as text, or contain hardcoded strings). Skip utility imports, style imports, icon imports, and non-text components.
|
|
28
|
+
3. Check which components already have an \`index.ditto.md\` spec by looking for the file in each component's directory.
|
|
29
|
+
4. Read \`workspace.ditto.md\` at the project root (if it exists) for existing platform tags and universal rules.
|
|
30
|
+
5. Report:
|
|
31
|
+
- Target component and its location
|
|
32
|
+
- Child components needing specs (no existing \`index.ditto.md\`)
|
|
33
|
+
- Existing specs found (already covered)
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Phase 2: Surface Analysis
|
|
38
|
+
|
|
39
|
+
For each component that needs a new spec (target + unspecced children):
|
|
40
|
+
|
|
41
|
+
1. Read the TypeScript interface / props type definition.
|
|
42
|
+
2. Identify every **text surface** — each piece of user-facing copy the component renders:
|
|
43
|
+
- **String props** rendered in JSX → use the prop name as the surface key
|
|
44
|
+
- **\`children\`** when used as text → use \`$children\` as the surface key
|
|
45
|
+
- **Nested props** → use dot notation (e.g. \`primaryAction.label\`)
|
|
46
|
+
- **Hardcoded strings** in JSX → use a descriptive role name (e.g. \`headline\`, \`submitLabel\`, \`bodyText\`)
|
|
47
|
+
3. For each surface, suggest **tags** from the \`workspace.ditto.md\` tag inventory, matched by semantic role (e.g. \`heading\`, \`body\`, \`button\`, \`call-to-action\`). Prefer reusing existing tags. Only propose a new tag if nothing fits — and note it will be new.
|
|
48
|
+
4. Estimate **\`maxLength\`** where layout context provides constraints (e.g. single-line headings, button widths). Omit if no clear constraint.
|
|
49
|
+
|
|
50
|
+
Present the proposed spec for each component in exact YAML format:
|
|
51
|
+
|
|
52
|
+
\`\`\`yaml
|
|
53
|
+
---
|
|
54
|
+
component: ComponentName
|
|
55
|
+
tags: [relevant-tags]
|
|
56
|
+
surfaces:
|
|
57
|
+
surfaceKey:
|
|
58
|
+
tags: [semantic-tags]
|
|
59
|
+
maxLength: 60
|
|
60
|
+
# Managed by Ditto — do not edit below
|
|
61
|
+
rules: []
|
|
62
|
+
---
|
|
63
|
+
\`\`\`
|
|
64
|
+
|
|
65
|
+
**STOP HERE. Ask the user to review and approve the proposed surfaces before creating any files.** Let them add, remove, or modify surfaces and tags.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Phase 3: Create Specs
|
|
70
|
+
|
|
71
|
+
After user approval:
|
|
72
|
+
|
|
73
|
+
1. For each component, run:
|
|
74
|
+
\`\`\`
|
|
75
|
+
npx ditto-spec scaffold <ComponentName> --path <component-directory>
|
|
76
|
+
\`\`\`
|
|
77
|
+
2. Edit each created \`index.ditto.md\` to replace the empty \`surfaces: {}\` and \`tags: []\` with the approved values. Only edit developer-owned keys (\`component\`, \`tags\`, \`surfaces\`). Never write \`rules\` by hand.
|
|
78
|
+
3. Run \`npx ditto-spec pull\` to populate the \`rules\` section from the platform.
|
|
79
|
+
4. Run \`npx ditto-spec check\` to validate all spec files.
|
|
80
|
+
|
|
81
|
+
Report which specs were created and how many rules were pulled.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Phase 4: Rule Gap Analysis
|
|
86
|
+
|
|
87
|
+
Identify copy patterns that should be rules but aren't.
|
|
88
|
+
|
|
89
|
+
1. Read all rules now in the spec files (workspace-level from \`workspace.ditto.md\` + component-level and per-surface from each \`index.ditto.md\`).
|
|
90
|
+
2. Search the codebase for **instances where the target component is used** — find all files that import and render it.
|
|
91
|
+
3. For each usage instance, read the **actual copy** being passed to the component's text surfaces (string literals, template strings, variables with obvious values).
|
|
92
|
+
4. Analyze the copy across instances for patterns no existing rule covers:
|
|
93
|
+
- Inconsistent terminology across instances
|
|
94
|
+
- Tone mismatches (e.g. some formal, some casual)
|
|
95
|
+
- Anti-patterns: passive voice in CTAs, overly long descriptions, redundant words
|
|
96
|
+
- Surface-specific conventions that should be formalized (e.g. "all action buttons start with a verb")
|
|
97
|
+
5. If **no meaningful gaps** are found, say so and skip to Phase 6.
|
|
98
|
+
6. If gaps are found, propose new rules. For each:
|
|
99
|
+
- **name**: short rule name
|
|
100
|
+
- **description**: what the rule enforces
|
|
101
|
+
- **tags**: which tags it should apply to (determines which surfaces it matches)
|
|
102
|
+
- **examples**: \`{from, to}\` pairs drawn from actual code showing the pattern
|
|
103
|
+
|
|
104
|
+
If any proposed tags don't exist in the workspace yet, warn the user. Currently, only existing workspace tags can be assigned to rules. New tags may need to be created on the platform first.
|
|
105
|
+
|
|
106
|
+
**STOP HERE. Present proposals and ask the user which rules (if any) to create.**
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Phase 5: Create Rules and Re-Pull
|
|
111
|
+
|
|
112
|
+
For each approved rule:
|
|
113
|
+
|
|
114
|
+
1. Run:
|
|
115
|
+
\`\`\`
|
|
116
|
+
npx ditto-spec create-rule --name "<rule name>" --description "<description>" --tags "<tag1,tag2>" --examples '<json array of {from,to}>'
|
|
117
|
+
\`\`\`
|
|
118
|
+
If a rule uses tags that don't exist yet, warn the user and omit those tags from the command (the API rejects unknown tags).
|
|
119
|
+
|
|
120
|
+
2. After all rules are created, run \`npx ditto-spec pull\` to sync the new rules into spec files.
|
|
121
|
+
|
|
122
|
+
3. Verify the specs now contain the expected rules by reading the updated \`index.ditto.md\` files.
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Phase 6: Copy Audit
|
|
127
|
+
|
|
128
|
+
Evaluate all component instances against the full rule set.
|
|
129
|
+
|
|
130
|
+
1. Re-read the fully-populated spec files (workspace + component + surface rules).
|
|
131
|
+
2. Find all instances of the component in the codebase.
|
|
132
|
+
3. For each instance, evaluate the actual copy against every applicable rule:
|
|
133
|
+
- Workspace rules apply to all surfaces
|
|
134
|
+
- Component-level rules (no \`surface\` field) apply to all surfaces
|
|
135
|
+
- Per-surface rules (with \`surface\` field) apply only to that surface
|
|
136
|
+
- Respect \`maxLength\` constraints — flag any text that exceeds the limit
|
|
137
|
+
4. Report violations:
|
|
138
|
+
- File and line where the instance appears
|
|
139
|
+
- The text that violates the rule
|
|
140
|
+
- Which rule is violated
|
|
141
|
+
- Suggested corrected copy
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Reference: Ditto Spec Conventions
|
|
146
|
+
|
|
147
|
+
These conventions are essential for creating correct specs:
|
|
148
|
+
|
|
149
|
+
- **Surface keys**: prop name for props, \`$children\` for children text, dot-notation for nested props (\`primaryAction.label\`), descriptive role for hardcoded strings (\`headline\`, \`bodyText\`).
|
|
150
|
+
- **Tags**: lowercase, hyphenated. Match semantic role (e.g. \`heading\`, \`body\`, \`button\`, \`call-to-action\`, \`dialog-title\`). Check \`workspace.ditto.md\` \`tags\` key for the full inventory.
|
|
151
|
+
- **Developer-owned keys**: \`component\`, \`tags\`, \`surfaces\` — edit freely.
|
|
152
|
+
- **CLI-managed keys**: \`rules\` (and workspace \`tags\`) — **never write by hand**. Populated by \`ditto-spec pull\`.
|
|
153
|
+
- **Three-level rule hierarchy**: workspace rules (apply everywhere) → component-level rules (no \`surface\` field, apply to all surfaces in that component) → per-surface rules (with \`surface: "<key>"\`, apply only to that surface).
|
|
154
|
+
- **Parent-child layering**: if a parent component passes text to a child (e.g. a label to a Button), the parent declares a surface in its own spec. Both parent and child rules apply — the parent's rules (e.g. dialog-level tone) layer with the child's rules (e.g. button-level constraints). A child having its own spec does NOT exempt the parent from declaring surfaces for text it provides.
|
|
155
|
+
- **Tag matching**: rules match specs by tag intersection. Any shared tag triggers the match. If a rule matches both component-level and surface-level tags, it emits at component level (broader scope wins).
|
|
156
|
+
`;
|
package/dist/skills.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const SKILL_SPEC_COMPONENT = "---\ndescription: Analyze a component, create ditto specs for it and its dependencies, and auto-fill surfaces and tags\nargument-hint: <ComponentName or path/to/component>\nallowed-tools: [Read, Bash, Edit, Write, Grep, Glob, Agent]\n---\n\n# Spec Component\n\nAnalyze a design system component, scaffold `.ditto.md` content specs (including child components that lack specs), auto-fill surfaces and tags, and sync rules from the platform.\n\n## Input\n\n$ARGUMENTS\n\nAccept either a component name (e.g. `Button`) or a file path (e.g. `src/components/Button/index.tsx`). If a name is given, search the codebase to find the component. If multiple matches exist, ask the user to clarify.\n\n---\n\n## Phase 1: Discovery\n\nFind the target component and map its dependencies.\n\n1. Locate the component source file.\n2. Read the component code. Walk imports to find **child components that render user-facing text** (components that accept string props, children rendered as text, or contain hardcoded strings). Skip utility imports, style imports, icon imports, and non-text components.\n3. Check which components already have an `index.ditto.md` spec by looking for the file in each component's directory.\n4. Read `workspace.ditto.md` at the project root (if it exists) for existing platform tags and universal rules.\n5. Report:\n - Target component and its location\n - Whether the target already has a spec (and what surfaces it declares)\n - Child components needing specs (no existing `index.ditto.md`)\n - Existing specs found (already covered)\n\n---\n\n## Phase 2: Surface Analysis\n\nFor each component that needs a new spec (target + unspecced children):\n\n1. Read the TypeScript interface / props type definition.\n2. Identify every **text surface** \u2014 each piece of user-facing copy the component renders:\n - **String props** rendered in JSX \u2192 use the prop name as the surface key\n - **`children`** when used as text \u2192 use `$children` as the surface key\n - **Nested props** \u2192 use dot notation (e.g. `primaryAction.label`)\n - **Hardcoded strings** in JSX \u2192 use a descriptive role name (e.g. `headline`, `submitLabel`, `bodyText`)\n3. For each surface, suggest **tags** from the `workspace.ditto.md` tag inventory, matched by semantic role (e.g. `heading`, `body`, `button`, `call-to-action`). Prefer reusing existing tags. Only propose a new tag if nothing fits \u2014 and note it will be new.\n4. Estimate **`maxLength`** where layout context provides constraints (e.g. single-line headings, button widths). Omit if no clear constraint.\n\n**If the component already has a spec**, compare the existing surfaces against what the code declares. Report any new surfaces that aren't in the spec yet, and any spec surfaces that no longer exist in the code. Propose additions/removals.\n\nPresent the proposed spec for each component in exact YAML format:\n\n```yaml\n---\ncomponent: ComponentName\ntags: [relevant-tags]\nsurfaces:\n surfaceKey:\n tags: [semantic-tags]\n maxLength: 60\n# Managed by Ditto \u2014 do not edit below\nrules: []\n---\n```\n\n**STOP HERE. Ask the user to review and approve the proposed surfaces before creating or modifying any files.** Let them add, remove, or modify surfaces and tags.\n\n---\n\n## Phase 3: Create or Update Specs\n\nAfter user approval:\n\n**For new specs:**\n\n1. Run:\n ```\n npx ditto-spec scaffold <ComponentName> --path <component-directory>\n ```\n2. Edit each created `index.ditto.md` to replace the empty `surfaces: {}` and `tags: []` with the approved values. Only edit developer-owned keys (`component`, `tags`, `surfaces`). Never write `rules` or `locales` by hand.\n\n**For existing specs getting new surfaces:**\n\n1. Edit the existing `index.ditto.md` to add the approved new surfaces and tags.\n\n**Then for all specs (new and updated):**\n\n3. Run `npx ditto-spec pull` to populate the `rules` and `locales` sections from the platform.\n4. Run `npx ditto-spec check` to validate all spec files.\n\nReport which specs were created/updated and how many rules were pulled.\n\n---\n\n## Reference: Ditto Spec Conventions\n\n- **Surface keys**: prop name for props, `$children` for children text, dot-notation for nested props (`primaryAction.label`), descriptive role for hardcoded strings (`headline`, `bodyText`).\n- **Tags**: lowercase, hyphenated. Match semantic role (e.g. `heading`, `body`, `button`, `call-to-action`, `dialog-title`). Check `workspace.ditto.md` `tags` key for the full inventory.\n- **Developer-owned keys**: `component`, `tags`, `surfaces` \u2014 edit freely.\n- **CLI-managed keys**: `rules`, `locales` (and workspace `tags`) \u2014 **never write by hand**. Populated by `ditto-spec pull`.\n- **Rule shapes**: style rules have `name`, `description`, optional `examples`, and `section`. Terminology entries have `term`, `disallowed`, `description`, and `section`.\n- **Locale-scoped rules**: the `locales` key contains rules keyed by locale code (e.g. `de-DE`). These apply in addition to base `rules` when writing for that locale.\n- **Rule hierarchy**: workspace rules (apply everywhere) \u2192 component-level rules (no `surface` field) \u2192 per-surface rules (with `surface: \"<key>\"`). Locale-scoped rules follow the same hierarchy.\n- **Parent-child layering**: if a parent passes text to a child, the parent declares a surface in its own spec. A child having its own spec does NOT exempt the parent from declaring surfaces for text it provides.\n- **Tag matching**: rules match specs by tag intersection. Any shared tag triggers the match.\n";
|
|
2
|
+
export declare const SKILL_SPEC_AUDIT = "---\ndescription: Audit component copy against ditto spec rules and report violations\nargument-hint: <ComponentName or path> (optional \u2014 omit to audit all)\nallowed-tools: [Read, Bash, Grep, Glob, Agent]\n---\n\n# Spec Audit\n\nEvaluate user-facing copy in component instances against the rules in their `.ditto.md` specs. Reports violations with file locations and suggested corrections.\n\n## Input\n\n$ARGUMENTS\n\nIf a component name or path is given, audit only that component. If omitted, audit all components that have specs.\n\n---\n\n## Phase 1: Load Specs\n\n1. Read `workspace.ditto.md` at the project root for workspace-level rules, tags, and locale-scoped rules.\n2. If a specific component was given, find its `index.ditto.md`. If auditing all, run `npx ditto-spec list` to discover all specced components, then read each `index.ditto.md`.\n3. For each component, build the full rule set:\n - Workspace rules (apply to all surfaces)\n - Component-level rules (no `surface` field \u2014 apply to all surfaces in this component)\n - Per-surface rules (with `surface: \"<key>\"` \u2014 apply only to that surface)\n - Locale-scoped rules from `locales` (if present, at workspace or component level)\n\n---\n\n## Phase 2: Find Instances\n\n1. Search the codebase for all files that import and render each component being audited.\n2. For each instance, read the **actual copy** being passed to the component's text surfaces:\n - String literals and template strings passed as props\n - Children rendered as text\n - Variables with obvious values (trace one level if needed)\n - Hardcoded strings in the component source itself\n\n---\n\n## Phase 3: Evaluate\n\nFor each instance, evaluate the copy against every applicable rule:\n\n- **Style rules** (`name`/`description`/`examples`): check that copy follows the described guidance. Use `examples` (before/after pairs) as concrete reference for tone, length, and shape.\n- **Terminology entries** (`term`/`disallowed`): check that copy uses the `term` form and never uses any `disallowed` alternative. Check all surface text, not just exact matches \u2014 look for the disallowed forms appearing within longer strings.\n- **`maxLength` / `minLength`**: flag any text that exceeds or falls short of the constraint.\n- **Locale-scoped rules**: if the copy targets a specific locale (identifiable from file path, i18n keys, or surrounding context), also evaluate against the matching `locales.<code>` rules.\n\nEach rule carries a `section` field (e.g. \"Voice & Tone\", \"Terminology\") \u2014 use this to group violations in the report.\n\n---\n\n## Phase 4: Report\n\nPresent violations grouped by component, then by file:\n\nFor each violation:\n- **File and line** where the instance appears\n- **Surface** the text maps to\n- **Text** that violates the rule\n- **Rule** that is violated (name or term, plus section)\n- **Suggested correction**\n\nIf no violations are found, say so explicitly \u2014 a clean audit is useful information.\n\nEnd with a summary: total components audited, total instances checked, total violations found.\n\n---\n\n## Reference: Ditto Spec Conventions\n\n- **Rule shapes**: style rules have `name`, `description`, optional `examples`, and `section`. Terminology entries have `term`, `disallowed`, `description`, and `section`.\n- **Locale-scoped rules**: the `locales` key contains rules keyed by locale code (e.g. `de-DE`). These apply in addition to base `rules` when writing for that locale.\n- **Rule hierarchy**: workspace rules (apply everywhere) \u2192 component-level rules (no `surface` field) \u2192 per-surface rules (with `surface: \"<key>\"`). Locale-scoped rules follow the same hierarchy.\n- **Tag matching**: rules match specs by tag intersection. Any shared tag triggers the match. If a rule matches both component-level and surface-level tags, it emits at component level (broader scope wins).\n";
|
|
3
|
+
export declare const SKILL_SPEC_GAPS = "---\ndescription: Analyze copy across component instances to find rule gaps, then create new rules on the platform\nargument-hint: <ComponentName or path> (optional \u2014 omit to analyze all)\nallowed-tools: [Read, Bash, Edit, Grep, Glob, Agent]\n---\n\n# Spec Gaps\n\nIdentify copy patterns in component instances that should be rules but aren't. Propose new style rules or terminology entries, and create approved ones on the Ditto platform.\n\n## Input\n\n$ARGUMENTS\n\nIf a component name or path is given, analyze only that component's instances. If omitted, analyze all components that have specs.\n\n---\n\n## Phase 1: Load Existing Rules\n\n1. Read `workspace.ditto.md` for workspace-level rules and tags.\n2. Read each relevant component's `index.ditto.md` for component-level and per-surface rules.\n3. Build a complete picture of what's already covered \u2014 every style rule, terminology entry, and locale-scoped rule currently in the spec files.\n\n---\n\n## Phase 2: Analyze Copy\n\n1. Search the codebase for **instances where each component is used** \u2014 find all files that import and render it.\n2. For each instance, read the **actual copy** being passed to the component's text surfaces (string literals, template strings, variables with obvious values).\n3. Analyze the copy across all instances for patterns no existing rule covers:\n - **Terminology inconsistencies**: the same concept referred to with different forms (e.g. \"sign up\" vs \"signup\" vs \"sign-up\"). These should become terminology entries.\n - **Tone mismatches**: some instances formal, others casual. These should become style rules.\n - **Anti-patterns**: passive voice in CTAs, overly long descriptions, redundant words.\n - **Surface-specific conventions** that should be formalized (e.g. \"all action buttons start with a verb\").\n\n---\n\n## Phase 3: Propose Rules\n\nIf **no meaningful gaps** are found, say so and stop.\n\nIf gaps are found, propose new rules. Rules come in two shapes:\n\n**Style rules:**\n- **name**: short rule name\n- **description**: what the rule enforces\n- **tags**: which tags it should apply to (determines which surfaces it matches)\n- **examples**: `{from, to}` pairs drawn from actual code showing the pattern\n\n**Terminology entries:**\n- **term**: the canonical form\n- **disallowed**: list of variant forms to reject\n- **description**: why this form is preferred\n- **tags**: which tags it should apply to\n\nIf any proposed tags don't exist in the workspace yet, warn the user. Only existing workspace tags can be assigned to rules.\n\n**STOP HERE. Present proposals and ask the user which rules (if any) to create.**\n\n---\n\n## Phase 4: Create Rules\n\nFor each approved rule, run:\n\n```\nnpx ditto-spec create-rule --name \"<rule name>\" --description \"<description>\" --tags \"<tag1,tag2>\" --examples '<json array of {from,to}>'\n```\n\nIf a rule uses tags that don't exist yet, warn the user and omit those tags from the command.\n\nAfter all rules are created:\n\n1. Run `npx ditto-spec pull` to sync the new rules into spec files.\n2. Read the updated `index.ditto.md` files and verify the new rules appear.\n3. Report which rules were created and which specs they landed in.\n\n---\n\n## Reference: Ditto Spec Conventions\n\n- **Rule shapes**: style rules have `name`, `description`, optional `examples`, and `section`. Terminology entries have `term`, `disallowed`, `description`, and `section`.\n- **CLI-managed keys**: `rules`, `locales` (and workspace `tags`) \u2014 **never write by hand**. Populated by `ditto-spec pull`.\n- **Tag matching**: rules match specs by tag intersection. Any shared tag triggers the match. Rules with no tags are workspace-universal (apply to all surfaces).\n- **Rule hierarchy**: workspace rules (apply everywhere) \u2192 component-level rules (no `surface` field) \u2192 per-surface rules (with `surface: \"<key>\"`).\n";
|
|
4
|
+
export declare const SKILLS: Record<string, string>;
|
package/dist/skills.js
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SKILLS = exports.SKILL_SPEC_GAPS = exports.SKILL_SPEC_AUDIT = exports.SKILL_SPEC_COMPONENT = void 0;
|
|
4
|
+
exports.SKILL_SPEC_COMPONENT = `---
|
|
5
|
+
description: Analyze a component, create ditto specs for it and its dependencies, and auto-fill surfaces and tags
|
|
6
|
+
argument-hint: <ComponentName or path/to/component>
|
|
7
|
+
allowed-tools: [Read, Bash, Edit, Write, Grep, Glob, Agent]
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Spec Component
|
|
11
|
+
|
|
12
|
+
Analyze a design system component, scaffold \`.ditto.md\` content specs (including child components that lack specs), auto-fill surfaces and tags, and sync rules from the platform.
|
|
13
|
+
|
|
14
|
+
## Input
|
|
15
|
+
|
|
16
|
+
$ARGUMENTS
|
|
17
|
+
|
|
18
|
+
Accept either a component name (e.g. \`Button\`) or a file path (e.g. \`src/components/Button/index.tsx\`). If a name is given, search the codebase to find the component. If multiple matches exist, ask the user to clarify.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Phase 1: Discovery
|
|
23
|
+
|
|
24
|
+
Find the target component and map its dependencies.
|
|
25
|
+
|
|
26
|
+
1. Locate the component source file.
|
|
27
|
+
2. Read the component code. Walk imports to find **child components that render user-facing text** (components that accept string props, children rendered as text, or contain hardcoded strings). Skip utility imports, style imports, icon imports, and non-text components.
|
|
28
|
+
3. Check which components already have an \`index.ditto.md\` spec by looking for the file in each component's directory.
|
|
29
|
+
4. Read \`workspace.ditto.md\` at the project root (if it exists) for existing platform tags and universal rules.
|
|
30
|
+
5. Report:
|
|
31
|
+
- Target component and its location
|
|
32
|
+
- Whether the target already has a spec (and what surfaces it declares)
|
|
33
|
+
- Child components needing specs (no existing \`index.ditto.md\`)
|
|
34
|
+
- Existing specs found (already covered)
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Phase 2: Surface Analysis
|
|
39
|
+
|
|
40
|
+
For each component that needs a new spec (target + unspecced children):
|
|
41
|
+
|
|
42
|
+
1. Read the TypeScript interface / props type definition.
|
|
43
|
+
2. Identify every **text surface** — each piece of user-facing copy the component renders:
|
|
44
|
+
- **String props** rendered in JSX → use the prop name as the surface key
|
|
45
|
+
- **\`children\`** when used as text → use \`$children\` as the surface key
|
|
46
|
+
- **Nested props** → use dot notation (e.g. \`primaryAction.label\`)
|
|
47
|
+
- **Hardcoded strings** in JSX → use a descriptive role name (e.g. \`headline\`, \`submitLabel\`, \`bodyText\`)
|
|
48
|
+
3. For each surface, suggest **tags** from the \`workspace.ditto.md\` tag inventory, matched by semantic role (e.g. \`heading\`, \`body\`, \`button\`, \`call-to-action\`). Prefer reusing existing tags. Only propose a new tag if nothing fits — and note it will be new.
|
|
49
|
+
4. Estimate **\`maxLength\`** where layout context provides constraints (e.g. single-line headings, button widths). Omit if no clear constraint.
|
|
50
|
+
|
|
51
|
+
**If the component already has a spec**, compare the existing surfaces against what the code declares. Report any new surfaces that aren't in the spec yet, and any spec surfaces that no longer exist in the code. Propose additions/removals.
|
|
52
|
+
|
|
53
|
+
Present the proposed spec for each component in exact YAML format:
|
|
54
|
+
|
|
55
|
+
\`\`\`yaml
|
|
56
|
+
---
|
|
57
|
+
component: ComponentName
|
|
58
|
+
tags: [relevant-tags]
|
|
59
|
+
surfaces:
|
|
60
|
+
surfaceKey:
|
|
61
|
+
tags: [semantic-tags]
|
|
62
|
+
maxLength: 60
|
|
63
|
+
# Managed by Ditto — do not edit below
|
|
64
|
+
rules: []
|
|
65
|
+
---
|
|
66
|
+
\`\`\`
|
|
67
|
+
|
|
68
|
+
**STOP HERE. Ask the user to review and approve the proposed surfaces before creating or modifying any files.** Let them add, remove, or modify surfaces and tags.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Phase 3: Create or Update Specs
|
|
73
|
+
|
|
74
|
+
After user approval:
|
|
75
|
+
|
|
76
|
+
**For new specs:**
|
|
77
|
+
|
|
78
|
+
1. Run:
|
|
79
|
+
\`\`\`
|
|
80
|
+
npx ditto-spec scaffold <ComponentName> --path <component-directory>
|
|
81
|
+
\`\`\`
|
|
82
|
+
2. Edit each created \`index.ditto.md\` to replace the empty \`surfaces: {}\` and \`tags: []\` with the approved values. Only edit developer-owned keys (\`component\`, \`tags\`, \`surfaces\`). Never write \`rules\` or \`locales\` by hand.
|
|
83
|
+
|
|
84
|
+
**For existing specs getting new surfaces:**
|
|
85
|
+
|
|
86
|
+
1. Edit the existing \`index.ditto.md\` to add the approved new surfaces and tags.
|
|
87
|
+
|
|
88
|
+
**Then for all specs (new and updated):**
|
|
89
|
+
|
|
90
|
+
3. Run \`npx ditto-spec pull\` to populate the \`rules\` and \`locales\` sections from the platform.
|
|
91
|
+
4. Run \`npx ditto-spec check\` to validate all spec files.
|
|
92
|
+
|
|
93
|
+
Report which specs were created/updated and how many rules were pulled.
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Reference: Ditto Spec Conventions
|
|
98
|
+
|
|
99
|
+
- **Surface keys**: prop name for props, \`$children\` for children text, dot-notation for nested props (\`primaryAction.label\`), descriptive role for hardcoded strings (\`headline\`, \`bodyText\`).
|
|
100
|
+
- **Tags**: lowercase, hyphenated. Match semantic role (e.g. \`heading\`, \`body\`, \`button\`, \`call-to-action\`, \`dialog-title\`). Check \`workspace.ditto.md\` \`tags\` key for the full inventory.
|
|
101
|
+
- **Developer-owned keys**: \`component\`, \`tags\`, \`surfaces\` — edit freely.
|
|
102
|
+
- **CLI-managed keys**: \`rules\`, \`locales\` (and workspace \`tags\`) — **never write by hand**. Populated by \`ditto-spec pull\`.
|
|
103
|
+
- **Rule shapes**: style rules have \`name\`, \`description\`, optional \`examples\`, and \`section\`. Terminology entries have \`term\`, \`disallowed\`, \`description\`, and \`section\`.
|
|
104
|
+
- **Locale-scoped rules**: the \`locales\` key contains rules keyed by locale code (e.g. \`de-DE\`). These apply in addition to base \`rules\` when writing for that locale.
|
|
105
|
+
- **Rule hierarchy**: workspace rules (apply everywhere) → component-level rules (no \`surface\` field) → per-surface rules (with \`surface: "<key>"\`). Locale-scoped rules follow the same hierarchy.
|
|
106
|
+
- **Parent-child layering**: if a parent passes text to a child, the parent declares a surface in its own spec. A child having its own spec does NOT exempt the parent from declaring surfaces for text it provides.
|
|
107
|
+
- **Tag matching**: rules match specs by tag intersection. Any shared tag triggers the match.
|
|
108
|
+
`;
|
|
109
|
+
exports.SKILL_SPEC_AUDIT = `---
|
|
110
|
+
description: Audit component copy against ditto spec rules and report violations
|
|
111
|
+
argument-hint: <ComponentName or path> (optional — omit to audit all)
|
|
112
|
+
allowed-tools: [Read, Bash, Grep, Glob, Agent]
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
# Spec Audit
|
|
116
|
+
|
|
117
|
+
Evaluate user-facing copy in component instances against the rules in their \`.ditto.md\` specs. Reports violations with file locations and suggested corrections.
|
|
118
|
+
|
|
119
|
+
## Input
|
|
120
|
+
|
|
121
|
+
$ARGUMENTS
|
|
122
|
+
|
|
123
|
+
If a component name or path is given, audit only that component. If omitted, audit all components that have specs.
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Phase 1: Load Specs
|
|
128
|
+
|
|
129
|
+
1. Read \`workspace.ditto.md\` at the project root for workspace-level rules, tags, and locale-scoped rules.
|
|
130
|
+
2. If a specific component was given, find its \`index.ditto.md\`. If auditing all, run \`npx ditto-spec list\` to discover all specced components, then read each \`index.ditto.md\`.
|
|
131
|
+
3. For each component, build the full rule set:
|
|
132
|
+
- Workspace rules (apply to all surfaces)
|
|
133
|
+
- Component-level rules (no \`surface\` field — apply to all surfaces in this component)
|
|
134
|
+
- Per-surface rules (with \`surface: "<key>"\` — apply only to that surface)
|
|
135
|
+
- Locale-scoped rules from \`locales\` (if present, at workspace or component level)
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Phase 2: Find Instances
|
|
140
|
+
|
|
141
|
+
1. Search the codebase for all files that import and render each component being audited.
|
|
142
|
+
2. For each instance, read the **actual copy** being passed to the component's text surfaces:
|
|
143
|
+
- String literals and template strings passed as props
|
|
144
|
+
- Children rendered as text
|
|
145
|
+
- Variables with obvious values (trace one level if needed)
|
|
146
|
+
- Hardcoded strings in the component source itself
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Phase 3: Evaluate
|
|
151
|
+
|
|
152
|
+
For each instance, evaluate the copy against every applicable rule:
|
|
153
|
+
|
|
154
|
+
- **Style rules** (\`name\`/\`description\`/\`examples\`): check that copy follows the described guidance. Use \`examples\` (before/after pairs) as concrete reference for tone, length, and shape.
|
|
155
|
+
- **Terminology entries** (\`term\`/\`disallowed\`): check that copy uses the \`term\` form and never uses any \`disallowed\` alternative. Check all surface text, not just exact matches — look for the disallowed forms appearing within longer strings.
|
|
156
|
+
- **\`maxLength\` / \`minLength\`**: flag any text that exceeds or falls short of the constraint.
|
|
157
|
+
- **Locale-scoped rules**: if the copy targets a specific locale (identifiable from file path, i18n keys, or surrounding context), also evaluate against the matching \`locales.<code>\` rules.
|
|
158
|
+
|
|
159
|
+
Each rule carries a \`section\` field (e.g. "Voice & Tone", "Terminology") — use this to group violations in the report.
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Phase 4: Report
|
|
164
|
+
|
|
165
|
+
Present violations grouped by component, then by file:
|
|
166
|
+
|
|
167
|
+
For each violation:
|
|
168
|
+
- **File and line** where the instance appears
|
|
169
|
+
- **Surface** the text maps to
|
|
170
|
+
- **Text** that violates the rule
|
|
171
|
+
- **Rule** that is violated (name or term, plus section)
|
|
172
|
+
- **Suggested correction**
|
|
173
|
+
|
|
174
|
+
If no violations are found, say so explicitly — a clean audit is useful information.
|
|
175
|
+
|
|
176
|
+
End with a summary: total components audited, total instances checked, total violations found.
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## Reference: Ditto Spec Conventions
|
|
181
|
+
|
|
182
|
+
- **Rule shapes**: style rules have \`name\`, \`description\`, optional \`examples\`, and \`section\`. Terminology entries have \`term\`, \`disallowed\`, \`description\`, and \`section\`.
|
|
183
|
+
- **Locale-scoped rules**: the \`locales\` key contains rules keyed by locale code (e.g. \`de-DE\`). These apply in addition to base \`rules\` when writing for that locale.
|
|
184
|
+
- **Rule hierarchy**: workspace rules (apply everywhere) → component-level rules (no \`surface\` field) → per-surface rules (with \`surface: "<key>"\`). Locale-scoped rules follow the same hierarchy.
|
|
185
|
+
- **Tag matching**: rules match specs by tag intersection. Any shared tag triggers the match. If a rule matches both component-level and surface-level tags, it emits at component level (broader scope wins).
|
|
186
|
+
`;
|
|
187
|
+
exports.SKILL_SPEC_GAPS = `---
|
|
188
|
+
description: Analyze copy across component instances to find rule gaps, then create new rules on the platform
|
|
189
|
+
argument-hint: <ComponentName or path> (optional — omit to analyze all)
|
|
190
|
+
allowed-tools: [Read, Bash, Edit, Grep, Glob, Agent]
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
# Spec Gaps
|
|
194
|
+
|
|
195
|
+
Identify copy patterns in component instances that should be rules but aren't. Propose new style rules or terminology entries, and create approved ones on the Ditto platform.
|
|
196
|
+
|
|
197
|
+
## Input
|
|
198
|
+
|
|
199
|
+
$ARGUMENTS
|
|
200
|
+
|
|
201
|
+
If a component name or path is given, analyze only that component's instances. If omitted, analyze all components that have specs.
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Phase 1: Load Existing Rules
|
|
206
|
+
|
|
207
|
+
1. Read \`workspace.ditto.md\` for workspace-level rules and tags.
|
|
208
|
+
2. Read each relevant component's \`index.ditto.md\` for component-level and per-surface rules.
|
|
209
|
+
3. Build a complete picture of what's already covered — every style rule, terminology entry, and locale-scoped rule currently in the spec files.
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## Phase 2: Analyze Copy
|
|
214
|
+
|
|
215
|
+
1. Search the codebase for **instances where each component is used** — find all files that import and render it.
|
|
216
|
+
2. For each instance, read the **actual copy** being passed to the component's text surfaces (string literals, template strings, variables with obvious values).
|
|
217
|
+
3. Analyze the copy across all instances for patterns no existing rule covers:
|
|
218
|
+
- **Terminology inconsistencies**: the same concept referred to with different forms (e.g. "sign up" vs "signup" vs "sign-up"). These should become terminology entries.
|
|
219
|
+
- **Tone mismatches**: some instances formal, others casual. These should become style rules.
|
|
220
|
+
- **Anti-patterns**: passive voice in CTAs, overly long descriptions, redundant words.
|
|
221
|
+
- **Surface-specific conventions** that should be formalized (e.g. "all action buttons start with a verb").
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## Phase 3: Propose Rules
|
|
226
|
+
|
|
227
|
+
If **no meaningful gaps** are found, say so and stop.
|
|
228
|
+
|
|
229
|
+
If gaps are found, propose new rules. Rules come in two shapes:
|
|
230
|
+
|
|
231
|
+
**Style rules:**
|
|
232
|
+
- **name**: short rule name
|
|
233
|
+
- **description**: what the rule enforces
|
|
234
|
+
- **tags**: which tags it should apply to (determines which surfaces it matches)
|
|
235
|
+
- **examples**: \`{from, to}\` pairs drawn from actual code showing the pattern
|
|
236
|
+
|
|
237
|
+
**Terminology entries:**
|
|
238
|
+
- **term**: the canonical form
|
|
239
|
+
- **disallowed**: list of variant forms to reject
|
|
240
|
+
- **description**: why this form is preferred
|
|
241
|
+
- **tags**: which tags it should apply to
|
|
242
|
+
|
|
243
|
+
If any proposed tags don't exist in the workspace yet, warn the user. Only existing workspace tags can be assigned to rules.
|
|
244
|
+
|
|
245
|
+
**STOP HERE. Present proposals and ask the user which rules (if any) to create.**
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## Phase 4: Create Rules
|
|
250
|
+
|
|
251
|
+
For each approved rule, run:
|
|
252
|
+
|
|
253
|
+
\`\`\`
|
|
254
|
+
npx ditto-spec create-rule --name "<rule name>" --description "<description>" --tags "<tag1,tag2>" --examples '<json array of {from,to}>'
|
|
255
|
+
\`\`\`
|
|
256
|
+
|
|
257
|
+
If a rule uses tags that don't exist yet, warn the user and omit those tags from the command.
|
|
258
|
+
|
|
259
|
+
After all rules are created:
|
|
260
|
+
|
|
261
|
+
1. Run \`npx ditto-spec pull\` to sync the new rules into spec files.
|
|
262
|
+
2. Read the updated \`index.ditto.md\` files and verify the new rules appear.
|
|
263
|
+
3. Report which rules were created and which specs they landed in.
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## Reference: Ditto Spec Conventions
|
|
268
|
+
|
|
269
|
+
- **Rule shapes**: style rules have \`name\`, \`description\`, optional \`examples\`, and \`section\`. Terminology entries have \`term\`, \`disallowed\`, \`description\`, and \`section\`.
|
|
270
|
+
- **CLI-managed keys**: \`rules\`, \`locales\` (and workspace \`tags\`) — **never write by hand**. Populated by \`ditto-spec pull\`.
|
|
271
|
+
- **Tag matching**: rules match specs by tag intersection. Any shared tag triggers the match. Rules with no tags are workspace-universal (apply to all surfaces).
|
|
272
|
+
- **Rule hierarchy**: workspace rules (apply everywhere) → component-level rules (no \`surface\` field) → per-surface rules (with \`surface: "<key>"\`).
|
|
273
|
+
`;
|
|
274
|
+
exports.SKILLS = {
|
|
275
|
+
"spec-component.md": exports.SKILL_SPEC_COMPONENT,
|
|
276
|
+
"spec-audit.md": exports.SKILL_SPEC_AUDIT,
|
|
277
|
+
"spec-gaps.md": exports.SKILL_SPEC_GAPS,
|
|
278
|
+
};
|
package/package.json
CHANGED
|
@@ -1,19 +1,33 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dittowords/spec-cli",
|
|
3
|
-
"version": "0.0.1-alpha.
|
|
3
|
+
"version": "0.0.1-alpha.10",
|
|
4
4
|
"description": "CLI for syncing .ditto.md content specs with the Ditto platform.",
|
|
5
|
-
"main": "
|
|
5
|
+
"main": "dist/cli.js",
|
|
6
6
|
"bin": {
|
|
7
|
-
"ditto-spec": "
|
|
7
|
+
"ditto-spec": "dist/bin.js"
|
|
8
8
|
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
9
12
|
"scripts": {
|
|
13
|
+
"build": "tsc",
|
|
14
|
+
"prepublishOnly": "npm run build",
|
|
10
15
|
"ditto-spec": "tsx src/cli.ts"
|
|
11
16
|
},
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=18"
|
|
19
|
+
},
|
|
12
20
|
"dependencies": {
|
|
21
|
+
"@dittowords/spec-cli": "^0.0.1-alpha.0",
|
|
13
22
|
"dotenv": "^16.0.0",
|
|
14
23
|
"glob": "^11.0.1",
|
|
15
|
-
"js-yaml": "^4.1.0"
|
|
16
|
-
|
|
24
|
+
"js-yaml": "^4.1.0"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/js-yaml": "^4.0.9",
|
|
28
|
+
"@types/node": "^20.0.0",
|
|
29
|
+
"tsx": "^4.0.0",
|
|
30
|
+
"typescript": "^5.0.0"
|
|
17
31
|
},
|
|
18
32
|
"license": "Proprietary"
|
|
19
33
|
}
|