motif-design 0.2.2 → 0.2.3
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/bin/cli.js +82 -0
- package/bin/commands/doctor.js +248 -0
- package/bin/commands/init.js +780 -0
- package/bin/commands/list.js +93 -0
- package/bin/commands/status.js +128 -0
- package/bin/commands/update.js +97 -0
- package/bin/install.js +4 -741
- package/bin/lib/find-root.js +31 -0
- package/bin/lib/manifest.js +57 -0
- package/core/references/icon-libraries.md +5 -1
- package/core/references/verticals/devtools.md +315 -0
- package/core/references/verticals/education.md +293 -0
- package/core/references/verticals/marketplace.md +305 -0
- package/core/references/verticals/social.md +290 -0
- package/package.json +3 -6
- package/runtimes/claude-code/CLAUDE-MD-SNIPPET.md +6 -0
- package/runtimes/claude-code/hooks/motif-context-monitor.js +97 -12
- package/runtimes/claude-code/hooks/motif-session-start.js +108 -0
- package/scripts/motif-state.js +474 -0
package/bin/cli.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const { parseArgs, styleText } = require('node:util');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
|
|
7
|
+
// ─── Subcommand registry ────────────────────────────────────────
|
|
8
|
+
const COMMANDS = {
|
|
9
|
+
init: './commands/init.js',
|
|
10
|
+
status: './commands/status.js',
|
|
11
|
+
update: './commands/update.js',
|
|
12
|
+
doctor: './commands/doctor.js',
|
|
13
|
+
list: './commands/list.js',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// ─── Parse top-level args ───────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
const { values, positionals } = parseArgs({
|
|
19
|
+
args: process.argv.slice(2),
|
|
20
|
+
allowPositionals: true,
|
|
21
|
+
strict: false,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// ─── Handle --version / -v ──────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
if (values.version || values.v) {
|
|
27
|
+
const pkg = require(path.join(__dirname, '..', 'package.json'));
|
|
28
|
+
console.log(pkg.version);
|
|
29
|
+
process.exit(0);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ─── Help text ──────────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
function printHelp() {
|
|
35
|
+
console.log(`
|
|
36
|
+
${styleText('bold', 'motif')} - Domain-intelligent design system for AI coding assistants
|
|
37
|
+
|
|
38
|
+
${styleText('bold', 'USAGE')}
|
|
39
|
+
motif <command> [options]
|
|
40
|
+
npx motif-design@latest [options]
|
|
41
|
+
|
|
42
|
+
${styleText('bold', 'COMMANDS')}
|
|
43
|
+
${styleText('cyan', 'init')} Install Motif into the current project
|
|
44
|
+
${styleText('cyan', 'status')} Show version, workflow phase, and screens composed
|
|
45
|
+
${styleText('cyan', 'update')} Sync project files from updated global package
|
|
46
|
+
${styleText('cyan', 'doctor')} Check installation integrity and configuration
|
|
47
|
+
${styleText('cyan', 'list')} Show available design verticals
|
|
48
|
+
${styleText('cyan', 'help')} Show this help message
|
|
49
|
+
|
|
50
|
+
${styleText('bold', 'OPTIONS')}
|
|
51
|
+
-v, --version Show version number
|
|
52
|
+
-h, --help Show this help message
|
|
53
|
+
|
|
54
|
+
${styleText('bold', 'EXAMPLES')}
|
|
55
|
+
motif init Install with auto-detected runtime
|
|
56
|
+
motif init --runtime claude-code Explicit runtime selection
|
|
57
|
+
motif status Check installation status
|
|
58
|
+
motif list Browse available verticals
|
|
59
|
+
motif doctor Diagnose installation issues
|
|
60
|
+
motif update Update to latest version
|
|
61
|
+
`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ─── Handle --help / -h / help subcommand ───────────────────────
|
|
65
|
+
|
|
66
|
+
if (values.help || values.h || positionals[0] === 'help') {
|
|
67
|
+
printHelp();
|
|
68
|
+
process.exit(0);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ─── Route subcommand ───────────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
const subcommand = positionals[0];
|
|
74
|
+
|
|
75
|
+
if (subcommand && COMMANDS[subcommand]) {
|
|
76
|
+
// Known subcommand: route to handler with remaining args
|
|
77
|
+
require(COMMANDS[subcommand]).run(process.argv.slice(3));
|
|
78
|
+
} else {
|
|
79
|
+
// No subcommand or unrecognized first arg: backward-compat init mode
|
|
80
|
+
// Passes all args through so `npx motif-design@latest --force` still works
|
|
81
|
+
require(COMMANDS.init).run(process.argv.slice(2));
|
|
82
|
+
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const { parseArgs, styleText } = require('node:util');
|
|
6
|
+
const { findProjectRoot } = require('../lib/find-root.js');
|
|
7
|
+
const { readManifest, getPackageVersion, compareVersions, hashFile } = require('../lib/manifest.js');
|
|
8
|
+
|
|
9
|
+
// ─── Flag parsing ────────────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
function parseFlags(args) {
|
|
12
|
+
const { values } = parseArgs({
|
|
13
|
+
args,
|
|
14
|
+
options: {
|
|
15
|
+
help: { type: 'boolean', short: 'h', default: false },
|
|
16
|
+
},
|
|
17
|
+
strict: true,
|
|
18
|
+
});
|
|
19
|
+
return values;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function printHelp() {
|
|
23
|
+
console.log(`
|
|
24
|
+
${styleText('bold', 'motif doctor')} - Check installation integrity and configuration
|
|
25
|
+
|
|
26
|
+
${styleText('bold', 'USAGE')}
|
|
27
|
+
motif doctor [options]
|
|
28
|
+
|
|
29
|
+
${styleText('bold', 'OPTIONS')}
|
|
30
|
+
-h, --help Show this help message
|
|
31
|
+
|
|
32
|
+
${styleText('bold', 'CHECKS')}
|
|
33
|
+
File Integrity Verify all manifest files exist and match expected hashes
|
|
34
|
+
Hook Config Check CLAUDE.md sentinels and settings.json hooks
|
|
35
|
+
Version Compare project version with global package version
|
|
36
|
+
`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ─── Check result helpers ────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
function ok(msg) { return { status: 'OK', msg }; }
|
|
42
|
+
function warn(msg) { return { status: '!!', msg }; }
|
|
43
|
+
function fail(msg) { return { status: 'XX', msg }; }
|
|
44
|
+
|
|
45
|
+
function formatResult(result) {
|
|
46
|
+
const label = `[${result.status}]`;
|
|
47
|
+
if (result.status === 'OK') {
|
|
48
|
+
return ` ${styleText('green', label)} ${result.msg}`;
|
|
49
|
+
} else if (result.status === '!!') {
|
|
50
|
+
return ` ${styleText('yellow', label)} ${result.msg}`;
|
|
51
|
+
} else {
|
|
52
|
+
return ` ${styleText('red', label)} ${result.msg}`;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ─── Checks ──────────────────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
function checkFileIntegrity(projectRoot, manifest) {
|
|
59
|
+
const results = [];
|
|
60
|
+
const files = manifest.files || {};
|
|
61
|
+
const fileKeys = Object.keys(files);
|
|
62
|
+
|
|
63
|
+
if (fileKeys.length === 0) {
|
|
64
|
+
results.push(warn('No files recorded in manifest'));
|
|
65
|
+
return results;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
for (const relPath of fileKeys) {
|
|
69
|
+
const fullPath = path.join(projectRoot, relPath);
|
|
70
|
+
if (!fs.existsSync(fullPath)) {
|
|
71
|
+
results.push(fail(`Missing: ${relPath}`));
|
|
72
|
+
} else {
|
|
73
|
+
try {
|
|
74
|
+
const currentHash = hashFile(fullPath);
|
|
75
|
+
const expectedHash = files[relPath].hash || files[relPath];
|
|
76
|
+
if (currentHash === expectedHash) {
|
|
77
|
+
results.push(ok(`${relPath}`));
|
|
78
|
+
} else {
|
|
79
|
+
results.push(warn(`Modified: ${relPath}`));
|
|
80
|
+
}
|
|
81
|
+
} catch {
|
|
82
|
+
results.push(warn(`Could not hash: ${relPath}`));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return results;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function checkHookConfiguration(projectRoot) {
|
|
91
|
+
const results = [];
|
|
92
|
+
|
|
93
|
+
// Check CLAUDE.md sentinels
|
|
94
|
+
const claudeMdPath = path.join(projectRoot, 'CLAUDE.md');
|
|
95
|
+
if (fs.existsSync(claudeMdPath)) {
|
|
96
|
+
const content = fs.readFileSync(claudeMdPath, 'utf8');
|
|
97
|
+
const hasStart = content.includes('MOTIF-START');
|
|
98
|
+
const hasEnd = content.includes('MOTIF-END');
|
|
99
|
+
if (hasStart && hasEnd) {
|
|
100
|
+
results.push(ok('CLAUDE.md sentinels present'));
|
|
101
|
+
} else {
|
|
102
|
+
results.push(fail('CLAUDE.md missing MOTIF sentinel markers'));
|
|
103
|
+
}
|
|
104
|
+
} else {
|
|
105
|
+
results.push(fail('CLAUDE.md not found'));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Check settings.json
|
|
109
|
+
const settingsPath = path.join(projectRoot, '.claude', 'settings.json');
|
|
110
|
+
if (fs.existsSync(settingsPath)) {
|
|
111
|
+
let settings;
|
|
112
|
+
try {
|
|
113
|
+
settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
114
|
+
} catch {
|
|
115
|
+
results.push(fail('settings.json is invalid JSON'));
|
|
116
|
+
results.push(fail('PostToolUse hook not configured'));
|
|
117
|
+
results.push(fail('SessionStart hook not configured'));
|
|
118
|
+
results.push(fail('statusLine not configured'));
|
|
119
|
+
return results;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// PostToolUse hook
|
|
123
|
+
const postToolUse = settings.hooks?.PostToolUse;
|
|
124
|
+
if (Array.isArray(postToolUse) && postToolUse.some(h => (h.command || '').includes('motif'))) {
|
|
125
|
+
results.push(ok('PostToolUse hook configured'));
|
|
126
|
+
} else {
|
|
127
|
+
results.push(fail('PostToolUse hook not configured'));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// SessionStart hook
|
|
131
|
+
const sessionStart = settings.hooks?.SessionStart;
|
|
132
|
+
if (Array.isArray(sessionStart) && sessionStart.some(h => (h.command || '').includes('motif-session-start'))) {
|
|
133
|
+
results.push(ok('SessionStart hook configured'));
|
|
134
|
+
} else {
|
|
135
|
+
results.push(fail('SessionStart hook not configured'));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// statusLine
|
|
139
|
+
const statusLine = settings.statusLine;
|
|
140
|
+
if (statusLine && (statusLine.command || '').includes('motif-context-monitor')) {
|
|
141
|
+
results.push(ok('statusLine configured'));
|
|
142
|
+
} else {
|
|
143
|
+
results.push(fail('statusLine not configured'));
|
|
144
|
+
}
|
|
145
|
+
} else {
|
|
146
|
+
results.push(fail('settings.json not found'));
|
|
147
|
+
results.push(fail('PostToolUse hook not configured'));
|
|
148
|
+
results.push(fail('SessionStart hook not configured'));
|
|
149
|
+
results.push(fail('statusLine not configured'));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return results;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function checkVersionConsistency(manifest) {
|
|
156
|
+
const results = [];
|
|
157
|
+
const packageVersion = getPackageVersion();
|
|
158
|
+
|
|
159
|
+
if (!packageVersion) {
|
|
160
|
+
results.push(warn('Could not determine package version'));
|
|
161
|
+
return results;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const cmp = compareVersions(manifest.version, packageVersion);
|
|
165
|
+
|
|
166
|
+
if (cmp === 0) {
|
|
167
|
+
results.push(ok(`Versions match (v${packageVersion})`));
|
|
168
|
+
} else if (cmp === 1) {
|
|
169
|
+
results.push(warn(`Project version (v${manifest.version}) newer than package (v${packageVersion})`));
|
|
170
|
+
} else {
|
|
171
|
+
results.push(warn(`Update available: v${packageVersion} (installed: v${manifest.version})`));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return results;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// ─── Main ────────────────────────────────────────────────────────
|
|
178
|
+
|
|
179
|
+
function run(args) {
|
|
180
|
+
const flags = parseFlags(args);
|
|
181
|
+
|
|
182
|
+
if (flags.help) {
|
|
183
|
+
printHelp();
|
|
184
|
+
process.exit(0);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Find project root
|
|
188
|
+
const projectRoot = findProjectRoot(process.cwd());
|
|
189
|
+
if (!projectRoot) {
|
|
190
|
+
console.error(styleText('red', 'Not inside a project directory.'));
|
|
191
|
+
console.error('Run this command from a directory that contains .git/ or package.json');
|
|
192
|
+
process.exit(1);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Read manifest
|
|
196
|
+
const manifest = readManifest(projectRoot);
|
|
197
|
+
if (!manifest) {
|
|
198
|
+
console.error(styleText('red', 'No Motif installation found. Run \'motif init\' to install.'));
|
|
199
|
+
process.exit(1);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Header
|
|
203
|
+
console.log('');
|
|
204
|
+
console.log(styleText('bold', 'Motif Doctor'));
|
|
205
|
+
console.log('\u2500'.repeat(40));
|
|
206
|
+
|
|
207
|
+
let totalOk = 0;
|
|
208
|
+
let totalWarn = 0;
|
|
209
|
+
let totalFail = 0;
|
|
210
|
+
|
|
211
|
+
function printCategory(name, results) {
|
|
212
|
+
console.log('');
|
|
213
|
+
console.log(styleText('bold', ` ${name}`));
|
|
214
|
+
for (const r of results) {
|
|
215
|
+
console.log(formatResult(r));
|
|
216
|
+
if (r.status === 'OK') totalOk++;
|
|
217
|
+
else if (r.status === '!!') totalWarn++;
|
|
218
|
+
else totalFail++;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Run all checks
|
|
223
|
+
printCategory('File Integrity', checkFileIntegrity(projectRoot, manifest));
|
|
224
|
+
printCategory('Hook Configuration', checkHookConfiguration(projectRoot));
|
|
225
|
+
printCategory('Version Consistency', checkVersionConsistency(manifest));
|
|
226
|
+
|
|
227
|
+
// Summary
|
|
228
|
+
console.log('');
|
|
229
|
+
console.log('\u2500'.repeat(40));
|
|
230
|
+
const summaryParts = [];
|
|
231
|
+
summaryParts.push(`${totalOk} passed`);
|
|
232
|
+
if (totalWarn > 0) summaryParts.push(`${totalWarn} warnings`);
|
|
233
|
+
if (totalFail > 0) summaryParts.push(`${totalFail} failed`);
|
|
234
|
+
const summary = summaryParts.join(', ');
|
|
235
|
+
|
|
236
|
+
if (totalFail > 0) {
|
|
237
|
+
console.log(styleText('red', summary));
|
|
238
|
+
} else if (totalWarn > 0) {
|
|
239
|
+
console.log(styleText('yellow', summary));
|
|
240
|
+
} else {
|
|
241
|
+
console.log(styleText('green', summary));
|
|
242
|
+
}
|
|
243
|
+
console.log('');
|
|
244
|
+
|
|
245
|
+
process.exit(totalFail > 0 ? 1 : 0);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
module.exports = { run };
|