@dittowords/spec-cli 0.0.1-alpha.0 → 0.0.1-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -1
- package/package.json +1 -1
- package/src/cli.ts +13 -0
- package/src/commands/init.ts +62 -11
- package/src/commands/scaffold.ts +40 -0
package/README.md
CHANGED
|
@@ -92,6 +92,16 @@ First-time setup. Creates `dittospec.config.json` and `workspace.ditto.md` in th
|
|
|
92
92
|
|
|
93
93
|
Use `--agent` to also write agent configuration directly: appends a Ditto Content Specs section to `CLAUDE.md` or `.cursorrules` (creates the file if absent).
|
|
94
94
|
|
|
95
|
+
### `ditto-spec scaffold <ComponentName>`
|
|
96
|
+
|
|
97
|
+
Creates a new `index.ditto.md` for a component with the correct YAML structure and empty managed keys.
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
ditto-spec scaffold DialogueModal --path src/components/DialogueModal
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Use `--path <dir>` to specify where the file is created (defaults to the current directory). After scaffolding, add surfaces for the component's text-bearing props and run `ditto-spec pull` to populate rules.
|
|
104
|
+
|
|
95
105
|
### `ditto-spec pull`
|
|
96
106
|
|
|
97
107
|
Syncs rules from the platform into spec files.
|
|
@@ -143,6 +153,26 @@ When writing or editing text props for a component:
|
|
|
143
153
|
|
|
144
154
|
### Creating specs
|
|
145
155
|
|
|
146
|
-
When
|
|
156
|
+
When creating a new component with text-bearing props, scaffold a spec file:
|
|
157
|
+
|
|
158
|
+
```
|
|
159
|
+
npx ditto-spec scaffold <ComponentName> --path <dir>
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Then edit the generated `index.ditto.md` to add surfaces — one entry per text-bearing prop:
|
|
163
|
+
|
|
164
|
+
```yaml
|
|
165
|
+
surfaces:
|
|
166
|
+
title:
|
|
167
|
+
tags: [heading]
|
|
168
|
+
maxLength: 60
|
|
169
|
+
$children:
|
|
170
|
+
tags: [button, cta]
|
|
171
|
+
maxLength: 30
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
- Use `$children` for text via children. Use dot notation for nested props (`primaryAction.label`).
|
|
175
|
+
- Choose `tags` from content categories like `heading`, `body`, `button`, `cta`, `dialog-title`, `call-to-action`.
|
|
176
|
+
- **Never write `rules` or `examples` by hand.** Run `ditto-spec pull` after adding surfaces to populate rules from the platform.
|
|
147
177
|
|
|
148
178
|
If a component lacks a spec and you'd have found one useful, propose creating one. If a spec lacks a rule you'd have wanted, propose adding the rule to the platform style guide with appropriate tags.
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -2,12 +2,23 @@ import { check } from "./commands/check";
|
|
|
2
2
|
import { init } from "./commands/init";
|
|
3
3
|
import { list } from "./commands/list";
|
|
4
4
|
import { pull } from "./commands/pull";
|
|
5
|
+
import { scaffold } from "./commands/scaffold";
|
|
5
6
|
|
|
6
7
|
const COMMANDS: Record<string, (args: string[]) => Promise<void>> = {
|
|
7
8
|
init: async (args) => init({ writeAgent: args.includes("--agent") }),
|
|
8
9
|
pull: async (args) => pull({ dryRun: args.includes("--dry-run") }),
|
|
9
10
|
check: async () => check(),
|
|
10
11
|
list: async () => list(),
|
|
12
|
+
scaffold: async (args) => {
|
|
13
|
+
const name = args.find((a) => !a.startsWith("--"));
|
|
14
|
+
if (!name) {
|
|
15
|
+
process.stderr.write("Usage: ditto-spec scaffold <ComponentName> [--path <dir>]\n");
|
|
16
|
+
process.exit(2);
|
|
17
|
+
}
|
|
18
|
+
const pathIdx = args.indexOf("--path");
|
|
19
|
+
const targetDir = pathIdx !== -1 && args[pathIdx + 1] ? args[pathIdx + 1] : process.cwd();
|
|
20
|
+
return scaffold({ componentName: name, targetDir });
|
|
21
|
+
},
|
|
11
22
|
};
|
|
12
23
|
|
|
13
24
|
const HELP = `ditto-spec — sync .ditto.md content specs with the Ditto platform.
|
|
@@ -18,6 +29,8 @@ Usage:
|
|
|
18
29
|
Commands:
|
|
19
30
|
init Set up ditto specs: creates config, workspace spec, and prints agent setup.
|
|
20
31
|
init --agent Also writes agent configuration (CLAUDE.md, .cursorrules, etc.).
|
|
32
|
+
scaffold <Name> Create a new index.ditto.md for a component.
|
|
33
|
+
scaffold <Name> --path <dir> Create the spec in a specific directory.
|
|
21
34
|
pull Pull rules from the platform into the managed keys of each spec.
|
|
22
35
|
pull --dry-run Show which files would change without writing.
|
|
23
36
|
check Parse every spec file; exit non-zero on any malformed file.
|
package/src/commands/init.ts
CHANGED
|
@@ -26,7 +26,9 @@ rules: []
|
|
|
26
26
|
const CLAUDE_MD_BLOCK = `
|
|
27
27
|
## Ditto Content Specs
|
|
28
28
|
|
|
29
|
-
This project uses \`.ditto.md\` files
|
|
29
|
+
This project uses \`.ditto.md\` files to declare text surfaces on components. Rules are synced from the Ditto platform — never written by hand.
|
|
30
|
+
|
|
31
|
+
### Reading specs
|
|
30
32
|
|
|
31
33
|
When writing or editing text props for a component:
|
|
32
34
|
|
|
@@ -34,15 +36,36 @@ When writing or editing text props for a component:
|
|
|
34
36
|
2. Check for \`index.ditto.md\` next to the component for surface-specific rules and constraints.
|
|
35
37
|
3. Respect \`maxLength\` — it's a layout constraint, not a suggestion.
|
|
36
38
|
4. Follow all rules in the \`rules\` key. Rules without a \`surface\` field apply to every surface; rules with \`surface\` apply only to that surface.
|
|
37
|
-
5. When creating a new component with text-bearing props, scaffold an \`index.ditto.md\` alongside it.
|
|
38
39
|
|
|
39
|
-
|
|
40
|
+
### Creating specs
|
|
41
|
+
|
|
42
|
+
When creating a new component with text-bearing props, scaffold a spec file:
|
|
43
|
+
|
|
44
|
+
npx ditto-spec scaffold <ComponentName> --path <dir>
|
|
45
|
+
|
|
46
|
+
Then edit the generated \`index.ditto.md\` to add surfaces — one entry per text-bearing prop:
|
|
47
|
+
|
|
48
|
+
surfaces:
|
|
49
|
+
title:
|
|
50
|
+
tags: [heading]
|
|
51
|
+
maxLength: 60
|
|
52
|
+
$children:
|
|
53
|
+
tags: [button, cta]
|
|
54
|
+
maxLength: 30
|
|
55
|
+
|
|
56
|
+
- Use \`$children\` for text via children. Use dot notation for nested props (\`primaryAction.label\`).
|
|
57
|
+
- Choose \`tags\` from content categories like \`heading\`, \`body\`, \`button\`, \`cta\`, \`dialog-title\`, \`call-to-action\`.
|
|
58
|
+
- **Never write \`rules\` or \`examples\` by hand.** Run \`npx ditto-spec pull\` after adding surfaces to populate rules from the platform.
|
|
59
|
+
|
|
60
|
+
Run \`npx ditto-spec list\` to see all existing specs.
|
|
40
61
|
`;
|
|
41
62
|
|
|
42
63
|
const CURSOR_RULES_BLOCK = `
|
|
43
64
|
# Ditto Content Specs
|
|
44
65
|
|
|
45
|
-
This project uses .ditto.md files
|
|
66
|
+
This project uses .ditto.md files to declare text surfaces on components. Rules are synced from the Ditto platform — never written by hand.
|
|
67
|
+
|
|
68
|
+
## Reading specs
|
|
46
69
|
|
|
47
70
|
When writing or editing text props for a component:
|
|
48
71
|
|
|
@@ -50,7 +73,28 @@ When writing or editing text props for a component:
|
|
|
50
73
|
2. Check for index.ditto.md next to the component for surface-specific rules and constraints.
|
|
51
74
|
3. Respect maxLength — it's a layout constraint, not a suggestion.
|
|
52
75
|
4. Follow all rules in the rules key. Rules without a surface field apply to every surface; rules with surface apply only to that surface.
|
|
53
|
-
|
|
76
|
+
|
|
77
|
+
## Creating specs
|
|
78
|
+
|
|
79
|
+
When creating a new component with text-bearing props, scaffold a spec file:
|
|
80
|
+
|
|
81
|
+
npx ditto-spec scaffold <ComponentName> --path <dir>
|
|
82
|
+
|
|
83
|
+
Then edit the generated index.ditto.md to add surfaces — one entry per text-bearing prop:
|
|
84
|
+
|
|
85
|
+
surfaces:
|
|
86
|
+
title:
|
|
87
|
+
tags: [heading]
|
|
88
|
+
maxLength: 60
|
|
89
|
+
$children:
|
|
90
|
+
tags: [button, cta]
|
|
91
|
+
maxLength: 30
|
|
92
|
+
|
|
93
|
+
- Use $children for text via children. Use dot notation for nested props (primaryAction.label).
|
|
94
|
+
- Choose tags from content categories like heading, body, button, cta, dialog-title, call-to-action.
|
|
95
|
+
- NEVER write rules or examples by hand. Run npx ditto-spec pull after adding surfaces to populate rules from the platform.
|
|
96
|
+
|
|
97
|
+
Run npx ditto-spec list to see all existing specs.
|
|
54
98
|
`;
|
|
55
99
|
|
|
56
100
|
const AGENTS_MD_ROW =
|
|
@@ -65,16 +109,21 @@ type AgentEnv = "claude" | "cursor" | "none";
|
|
|
65
109
|
export async function init(opts: InitOptions = {}): Promise<void> {
|
|
66
110
|
const cwd = process.cwd();
|
|
67
111
|
|
|
68
|
-
|
|
112
|
+
const configExists = fs.existsSync(path.join(cwd, CONFIG_NAME));
|
|
113
|
+
|
|
114
|
+
if (configExists && !opts.writeAgent) {
|
|
69
115
|
console.log(`${CONFIG_NAME} already exists. Nothing to do.`);
|
|
116
|
+
console.log("Run with --agent to add agent configuration (CLAUDE.md, .cursorrules, etc.).");
|
|
70
117
|
return;
|
|
71
118
|
}
|
|
72
119
|
|
|
73
|
-
|
|
74
|
-
|
|
120
|
+
if (!configExists) {
|
|
121
|
+
fs.writeFileSync(path.join(cwd, CONFIG_NAME), DEFAULT_CONFIG + "\n");
|
|
122
|
+
console.log(`✓ Created ${CONFIG_NAME}`);
|
|
75
123
|
|
|
76
|
-
|
|
77
|
-
|
|
124
|
+
fs.writeFileSync(path.join(cwd, WORKSPACE_SPEC_NAME), WORKSPACE_SPEC);
|
|
125
|
+
console.log(`✓ Created ${WORKSPACE_SPEC_NAME}`);
|
|
126
|
+
}
|
|
78
127
|
|
|
79
128
|
const env = detectAgentEnv(cwd);
|
|
80
129
|
|
|
@@ -84,7 +133,9 @@ export async function init(opts: InitOptions = {}): Promise<void> {
|
|
|
84
133
|
printAgentSuggestions(env);
|
|
85
134
|
}
|
|
86
135
|
|
|
87
|
-
|
|
136
|
+
if (!configExists) {
|
|
137
|
+
printNextSteps();
|
|
138
|
+
}
|
|
88
139
|
}
|
|
89
140
|
|
|
90
141
|
function detectAgentEnv(cwd: string): AgentEnv {
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
const SPEC_FILENAME = "index.ditto.md";
|
|
5
|
+
|
|
6
|
+
interface ScaffoldOptions {
|
|
7
|
+
componentName: string;
|
|
8
|
+
targetDir: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function scaffold(opts: ScaffoldOptions): Promise<void> {
|
|
12
|
+
const dest = path.join(opts.targetDir, SPEC_FILENAME);
|
|
13
|
+
|
|
14
|
+
if (fs.existsSync(dest)) {
|
|
15
|
+
console.log(`${SPEC_FILENAME} already exists at ${opts.targetDir}. Nothing to do.`);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const content = `---
|
|
20
|
+
component: ${opts.componentName}
|
|
21
|
+
description: >
|
|
22
|
+
TODO: describe this component.
|
|
23
|
+
tags: []
|
|
24
|
+
surfaces: {}
|
|
25
|
+
# Managed by Ditto — do not edit below
|
|
26
|
+
rules: []
|
|
27
|
+
examples: []
|
|
28
|
+
---
|
|
29
|
+
`;
|
|
30
|
+
|
|
31
|
+
fs.mkdirSync(opts.targetDir, { recursive: true });
|
|
32
|
+
fs.writeFileSync(dest, content);
|
|
33
|
+
console.log(`✓ Created ${path.relative(process.cwd(), dest)}`);
|
|
34
|
+
|
|
35
|
+
console.log(`
|
|
36
|
+
Next steps:
|
|
37
|
+
1. Add text-bearing props as surface keys under 'surfaces'
|
|
38
|
+
2. Tag each surface with content categories (heading, body, button, cta, etc.)
|
|
39
|
+
3. Run \`ditto-spec pull\` to populate rules from the platform`);
|
|
40
|
+
}
|