@gangvy/claude-code-measurement-marker-check 0.1.0 → 0.1.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/.claude-plugin/plugin.json +1 -1
- package/README.md +28 -0
- package/bin/claude-measure-marker.js +213 -27
- package/package.json +1 -1
- package/skill-package-manifest.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "measurement-marker-check",
|
|
3
3
|
"description": "P0 renovation measurement drawing marker check skill for Claude Code. Upload a measurement image, identify missing annotations, and produce a review checklist.",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.2",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "nkkj-BrainHack"
|
|
7
7
|
}
|
package/README.md
CHANGED
|
@@ -25,6 +25,34 @@ npm --prefix claude-code-measurement-marker-check run smoke
|
|
|
25
25
|
|
|
26
26
|
## Claude Code 加载
|
|
27
27
|
|
|
28
|
+
推荐给 VSCode Claude Code 使用工作区安装:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npx @gangvy/claude-code-measurement-marker-check workspace --smoke
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
安装后会生成:
|
|
35
|
+
|
|
36
|
+
```text
|
|
37
|
+
.\.claude\plugins\measurement-marker-check
|
|
38
|
+
.\.claude\skills\measurement-marker-check\SKILL.md
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
如果 Claude Code 会话已经打开,安装后重启当前 VSCode Claude Code 会话。
|
|
42
|
+
|
|
43
|
+
用户级安装:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
npm install -g @gangvy/claude-code-measurement-marker-check
|
|
47
|
+
claude-measure-marker install
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
免全局安装到用户级插件目录:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npx @gangvy/claude-code-measurement-marker-check install
|
|
54
|
+
```
|
|
55
|
+
|
|
28
56
|
```bash
|
|
29
57
|
claude --plugin-dir ./claude-code-measurement-marker-check
|
|
30
58
|
```
|
|
@@ -4,63 +4,136 @@ const os = require('os');
|
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const { spawnSync } = require('child_process');
|
|
6
6
|
|
|
7
|
-
const
|
|
7
|
+
const SOURCE_ROOT = path.resolve(__dirname, '..');
|
|
8
|
+
const WORKSPACE_ROOT = process.cwd();
|
|
8
9
|
const DEFAULT_TARGET = path.join(os.homedir(), '.claude', 'plugins', 'measurement-marker-check');
|
|
10
|
+
const WORKSPACE_TARGET = path.join(WORKSPACE_ROOT, '.claude', 'plugins', 'measurement-marker-check');
|
|
11
|
+
const WORKSPACE_PLUGINS_ROOT = path.join(WORKSPACE_ROOT, '.claude', 'plugins');
|
|
12
|
+
|
|
9
13
|
const COPY_ENTRIES = [
|
|
10
14
|
'.claude-plugin',
|
|
11
15
|
'README.md',
|
|
12
16
|
'scripts',
|
|
13
17
|
'skill-package-manifest.json',
|
|
14
18
|
'skills',
|
|
19
|
+
'test-fixtures',
|
|
15
20
|
'tools',
|
|
16
21
|
'package.json'
|
|
17
22
|
];
|
|
18
23
|
|
|
24
|
+
function expandHome(value) {
|
|
25
|
+
return String(value || '').replace(/^~(?=$|[\\/])/, os.homedir());
|
|
26
|
+
}
|
|
27
|
+
|
|
19
28
|
function parseArgs(argv) {
|
|
20
|
-
const
|
|
21
|
-
|
|
29
|
+
const first = argv[0] && !argv[0].startsWith('--') ? argv[0] : 'install';
|
|
30
|
+
const args = {
|
|
31
|
+
command: first,
|
|
32
|
+
target: DEFAULT_TARGET,
|
|
33
|
+
smoke: false,
|
|
34
|
+
force: false,
|
|
35
|
+
help: false
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
if (first === 'workspace' || first === 'install-workspace') {
|
|
39
|
+
args.command = 'install';
|
|
40
|
+
args.target = WORKSPACE_TARGET;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
for (let i = first === argv[0] ? 1 : 0; i < argv.length; i++) {
|
|
22
44
|
const token = argv[i];
|
|
23
45
|
if (token === '--target' && argv[i + 1]) {
|
|
24
|
-
args.target =
|
|
46
|
+
args.target = argv[++i];
|
|
47
|
+
} else if (token.startsWith('--target=')) {
|
|
48
|
+
args.target = token.slice('--target='.length);
|
|
49
|
+
} else if (token === '--workspace') {
|
|
50
|
+
args.target = WORKSPACE_TARGET;
|
|
25
51
|
} else if (token === '--smoke') {
|
|
26
52
|
args.smoke = true;
|
|
53
|
+
} else if (token === '--force') {
|
|
54
|
+
args.force = true;
|
|
55
|
+
} else if (token === '--help' || token === '-h') {
|
|
56
|
+
args.help = true;
|
|
27
57
|
}
|
|
28
58
|
}
|
|
59
|
+
|
|
60
|
+
args.target = path.resolve(expandHome(args.target));
|
|
29
61
|
return args;
|
|
30
62
|
}
|
|
31
63
|
|
|
32
64
|
function printHelp() {
|
|
33
65
|
console.log([
|
|
34
|
-
'Claude
|
|
66
|
+
'Claude measurement marker skill package installer',
|
|
35
67
|
'',
|
|
36
68
|
'Usage:',
|
|
37
69
|
' claude-measure-marker install [--target <dir>] [--smoke]',
|
|
70
|
+
' claude-measure-marker workspace [--smoke]',
|
|
71
|
+
' claude-measure-marker install --workspace [--smoke]',
|
|
38
72
|
' claude-measure-marker check',
|
|
39
73
|
' claude-measure-marker smoke',
|
|
40
74
|
' claude-measure-marker path',
|
|
41
75
|
'',
|
|
42
|
-
'
|
|
76
|
+
'npx:',
|
|
43
77
|
' npx @gangvy/claude-code-measurement-marker-check install',
|
|
44
|
-
' claude
|
|
78
|
+
' npx @gangvy/claude-code-measurement-marker-check workspace --smoke',
|
|
79
|
+
'',
|
|
80
|
+
'Options:',
|
|
81
|
+
' --workspace Install into ./.claude/plugins/measurement-marker-check',
|
|
82
|
+
' --target <dir> Install into a custom directory',
|
|
83
|
+
' --force Allow overwriting an existing custom target',
|
|
84
|
+
' --smoke Run sample smoke checks after install',
|
|
85
|
+
' --help, -h Show help'
|
|
45
86
|
].join('\n'));
|
|
46
87
|
}
|
|
47
88
|
|
|
48
|
-
function
|
|
49
|
-
|
|
50
|
-
|
|
89
|
+
function ensureDir(dirPath) {
|
|
90
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function isInside(parentDir, childDir) {
|
|
94
|
+
const relative = path.relative(path.resolve(parentDir), path.resolve(childDir));
|
|
95
|
+
return relative === '' || (!!relative && !relative.startsWith('..') && !path.isAbsolute(relative));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function isSafeDefaultTarget(targetDir) {
|
|
99
|
+
return path.resolve(targetDir) === path.resolve(DEFAULT_TARGET);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function isSafeWorkspaceTarget(targetDir) {
|
|
103
|
+
return isInside(WORKSPACE_PLUGINS_ROOT, targetDir);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function canOverwriteTarget(targetDir, force) {
|
|
107
|
+
return force || isSafeDefaultTarget(targetDir) || isSafeWorkspaceTarget(targetDir);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function copyDirRecursive(source, destination) {
|
|
111
|
+
const stat = fs.statSync(source);
|
|
112
|
+
if (stat.isDirectory()) {
|
|
113
|
+
ensureDir(destination);
|
|
114
|
+
for (const child of fs.readdirSync(source)) {
|
|
115
|
+
if (child === 'node_modules' || child === 'outputs' || child === '.git') continue;
|
|
116
|
+
copyDirRecursive(path.join(source, child), path.join(destination, child));
|
|
117
|
+
}
|
|
118
|
+
return;
|
|
51
119
|
}
|
|
52
|
-
|
|
120
|
+
ensureDir(path.dirname(destination));
|
|
121
|
+
fs.copyFileSync(source, destination);
|
|
53
122
|
}
|
|
54
123
|
|
|
55
|
-
function copyPlugin(target) {
|
|
56
|
-
|
|
57
|
-
|
|
124
|
+
function copyPlugin(target, force) {
|
|
125
|
+
if (fs.existsSync(target)) {
|
|
126
|
+
if (!canOverwriteTarget(target, force)) {
|
|
127
|
+
throw new Error(`Refusing to overwrite custom target without --force: ${target}`);
|
|
128
|
+
}
|
|
129
|
+
fs.rmSync(target, { recursive: true, force: true });
|
|
130
|
+
}
|
|
131
|
+
ensureDir(target);
|
|
58
132
|
|
|
59
133
|
for (const entry of COPY_ENTRIES) {
|
|
60
|
-
const source = path.join(
|
|
134
|
+
const source = path.join(SOURCE_ROOT, entry);
|
|
61
135
|
if (!fs.existsSync(source)) continue;
|
|
62
|
-
|
|
63
|
-
fs.cpSync(source, destination, { recursive: true });
|
|
136
|
+
copyDirRecursive(source, path.join(target, entry));
|
|
64
137
|
}
|
|
65
138
|
}
|
|
66
139
|
|
|
@@ -69,14 +142,13 @@ function checkPlugin(target) {
|
|
|
69
142
|
'.claude-plugin/plugin.json',
|
|
70
143
|
'skills/measurement-marker-check/SKILL.md',
|
|
71
144
|
'tools/measurement-marker-check-run.js',
|
|
145
|
+
'scripts/smoke.js',
|
|
72
146
|
'package.json'
|
|
73
147
|
];
|
|
74
|
-
|
|
75
148
|
const missing = required.filter(entry => !fs.existsSync(path.join(target, entry)));
|
|
76
149
|
if (missing.length) {
|
|
77
|
-
throw new Error(
|
|
150
|
+
throw new Error(`Install target is missing required files: ${missing.join(', ')}`);
|
|
78
151
|
}
|
|
79
|
-
|
|
80
152
|
return {
|
|
81
153
|
status: 'ok',
|
|
82
154
|
target,
|
|
@@ -84,6 +156,102 @@ function checkPlugin(target) {
|
|
|
84
156
|
};
|
|
85
157
|
}
|
|
86
158
|
|
|
159
|
+
function asForwardSlash(filePath) {
|
|
160
|
+
return path.resolve(filePath).replace(/\\/g, '/');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function quotedPath(filePath) {
|
|
164
|
+
return `"${asForwardSlash(filePath)}"`;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function workspaceSkillText(target) {
|
|
168
|
+
const runner = path.join(target, 'tools', 'measurement-marker-check-run.js');
|
|
169
|
+
return [
|
|
170
|
+
'---',
|
|
171
|
+
'name: measurement-marker-check',
|
|
172
|
+
'description: Check 装修量尺图、手绘量尺单、户型草图或现场测量图片中是否存在疑似未标记位置,输出缺标记清单、待复核区域和下一步补标建议。Use when the user asks for 实战二、装修量尺、量尺图识别、尺寸图缺标记、哪里没标、未标注检查 or measurement drawing review.',
|
|
173
|
+
'allowed-tools: Read Write Bash(node *)',
|
|
174
|
+
'---',
|
|
175
|
+
'',
|
|
176
|
+
'# 装修量尺图缺标记检查官',
|
|
177
|
+
'',
|
|
178
|
+
'你是面向设计/量尺团队的量尺图缺标记检查官。用户要的是业务复核清单,不是技术日志。',
|
|
179
|
+
'',
|
|
180
|
+
'## 工作流',
|
|
181
|
+
'',
|
|
182
|
+
'1. 如果用户没有上传图片或给出图片路径,先请用户上传量尺图、手绘量尺单、户型草图或现场测量截图。',
|
|
183
|
+
'2. 如果用户上传了图片,先直接观察图片内容,整理已标记内容、疑似缺标位置、待人工复核区域。',
|
|
184
|
+
'3. 把观察结果写成结构化 JSON,保存到 `outputs/measurement-marker-check/input.json`。',
|
|
185
|
+
'4. 调用本地工具生成稳定报告。',
|
|
186
|
+
'5. 优先把工具返回的 `assistantMessage` 正文发给用户,文件路径只放末尾作为归档信息。',
|
|
187
|
+
'',
|
|
188
|
+
'## 检查口径',
|
|
189
|
+
'',
|
|
190
|
+
'- 空间名称:客厅、卧室、厨房、卫生间、阳台等。',
|
|
191
|
+
'- 关键尺寸:长、宽、高、门洞、窗洞、墙面、柜体位置等。',
|
|
192
|
+
'- 门窗洞口:宽高、离地高度、开启方向、门套或窗台限制。',
|
|
193
|
+
'- 梁柱/墙体限制:梁高、柱位、墙厚、承重/非承重提示。',
|
|
194
|
+
'- 水电/管道/烟道:下水、地漏、插座、开关、燃气、烟道、排水管。',
|
|
195
|
+
'- 安装避让备注:需避让、不可拆、需复核、现场限制、手写备注。',
|
|
196
|
+
'',
|
|
197
|
+
'## 工具输入 JSON 结构',
|
|
198
|
+
'',
|
|
199
|
+
'```json',
|
|
200
|
+
'{',
|
|
201
|
+
' "project": "项目或样本名称",',
|
|
202
|
+
' "image": "图片文件名或路径",',
|
|
203
|
+
' "detectedMarkers": [',
|
|
204
|
+
' { "area": "厨房右侧墙面", "markerType": "关键尺寸", "value": "3200mm", "evidence": "墙面横向尺寸已标注", "confidence": 0.9 }',
|
|
205
|
+
' ],',
|
|
206
|
+
' "missingMarkers": [',
|
|
207
|
+
' { "area": "窗户下沿", "missingType": "窗台高度", "risk": "可能影响台面、吊柜或窗帘盒设计", "evidence": "窗户位置已画出,但没有离地高度标注", "confidence": 0.78 }',
|
|
208
|
+
' ],',
|
|
209
|
+
' "uncertainAreas": [',
|
|
210
|
+
' { "area": "厨房右上角", "reason": "疑似烟道或立管,但文字不清晰", "action": "请现场照片或原始量尺单复核", "confidence": 0.58 }',
|
|
211
|
+
' ],',
|
|
212
|
+
' "overallJudgement": "存在关键缺标记,建议人工复核后再进入标准尺寸表。"',
|
|
213
|
+
'}',
|
|
214
|
+
'```',
|
|
215
|
+
'',
|
|
216
|
+
'## 生成报告命令',
|
|
217
|
+
'',
|
|
218
|
+
'```bash',
|
|
219
|
+
`node ${quotedPath(runner)} --input "outputs/measurement-marker-check/input.json" --output "outputs/measurement-marker-check" --result-prefix MEASURE_MARKER_RESULT`,
|
|
220
|
+
'```',
|
|
221
|
+
'',
|
|
222
|
+
'## 演示样例命令',
|
|
223
|
+
'',
|
|
224
|
+
'```bash',
|
|
225
|
+
`node ${quotedPath(runner)} --sample --output "outputs/measurement-marker-check-sample" --result-prefix MEASURE_MARKER_RESULT`,
|
|
226
|
+
'```',
|
|
227
|
+
'',
|
|
228
|
+
'## 输出规则',
|
|
229
|
+
'',
|
|
230
|
+
'- 先给结论:是否存在疑似缺标记。',
|
|
231
|
+
'- 必须明确“区域 + 缺失类型 + 风险 + 图像依据 + 置信度”。',
|
|
232
|
+
'- 看不清的地方归入“待人工复核区域”,不要强行判断。',
|
|
233
|
+
'- 不要把 P0 结果说成最终工程结论;它是辅助复核清单。',
|
|
234
|
+
'- 不要只返回文件路径。',
|
|
235
|
+
'',
|
|
236
|
+
'## 推荐启动话术',
|
|
237
|
+
'',
|
|
238
|
+
'```text',
|
|
239
|
+
'帮我检查这张装修量尺图有没有哪里未标记。',
|
|
240
|
+
'重点看空间名称、门窗洞口、长宽高、管道、插座/烟道和现场限制备注。',
|
|
241
|
+
'先给我缺标记清单和人工复核建议。',
|
|
242
|
+
'```',
|
|
243
|
+
''
|
|
244
|
+
].join('\n');
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function writeWorkspaceSkillEntry(target) {
|
|
248
|
+
if (!isSafeWorkspaceTarget(target)) return;
|
|
249
|
+
const skillDir = path.join(WORKSPACE_ROOT, '.claude', 'skills', 'measurement-marker-check');
|
|
250
|
+
ensureDir(skillDir);
|
|
251
|
+
fs.writeFileSync(path.join(skillDir, 'SKILL.md'), workspaceSkillText(target), 'utf8');
|
|
252
|
+
console.log('Wrote workspace skill entry: .\\.claude\\skills\\measurement-marker-check\\SKILL.md');
|
|
253
|
+
}
|
|
254
|
+
|
|
87
255
|
function runSmoke(target) {
|
|
88
256
|
const result = spawnSync(process.execPath, ['scripts/smoke.js'], {
|
|
89
257
|
cwd: target,
|
|
@@ -96,18 +264,35 @@ function runSmoke(target) {
|
|
|
96
264
|
}
|
|
97
265
|
|
|
98
266
|
function printNextSteps(target) {
|
|
267
|
+
const workspaceMode = isSafeWorkspaceTarget(target);
|
|
99
268
|
console.log('');
|
|
100
|
-
console.log('
|
|
101
|
-
console.log(` claude --plugin-dir "${target}"`);
|
|
269
|
+
console.log('Install complete.');
|
|
102
270
|
console.log('');
|
|
103
|
-
|
|
271
|
+
if (workspaceMode) {
|
|
272
|
+
console.log('VSCode workspace mode is ready.');
|
|
273
|
+
console.log('');
|
|
274
|
+
console.log('Generated project files:');
|
|
275
|
+
console.log(' .\\.claude\\skills\\measurement-marker-check\\SKILL.md');
|
|
276
|
+
console.log(' .\\.claude\\plugins\\measurement-marker-check');
|
|
277
|
+
console.log('');
|
|
278
|
+
console.log('Restart the VSCode Claude Code session if it was already open.');
|
|
279
|
+
} else {
|
|
280
|
+
console.log('Claude Code CLI launch command:');
|
|
281
|
+
console.log(` claude --plugin-dir "${target}"`);
|
|
282
|
+
}
|
|
283
|
+
console.log('');
|
|
284
|
+
console.log('Try this prompt in Claude Code:');
|
|
104
285
|
console.log(' 帮我检查这张装修量尺图有没有哪里未标记。');
|
|
286
|
+
console.log(' 重点看空间名称、门窗洞口、长宽高、管道、插座/烟道和现场限制备注。');
|
|
287
|
+
console.log(' 先给我缺标记清单和人工复核建议。');
|
|
288
|
+
console.log('');
|
|
289
|
+
console.log(`Install target: ${target}`);
|
|
105
290
|
}
|
|
106
291
|
|
|
107
292
|
function main() {
|
|
108
293
|
const args = parseArgs(process.argv.slice(2));
|
|
109
294
|
|
|
110
|
-
if (args.
|
|
295
|
+
if (args.help || args.command === 'help') {
|
|
111
296
|
printHelp();
|
|
112
297
|
return;
|
|
113
298
|
}
|
|
@@ -118,9 +303,10 @@ function main() {
|
|
|
118
303
|
}
|
|
119
304
|
|
|
120
305
|
if (args.command === 'install') {
|
|
121
|
-
copyPlugin(args.target);
|
|
306
|
+
copyPlugin(args.target, args.force);
|
|
122
307
|
const check = checkPlugin(args.target);
|
|
123
308
|
console.log(JSON.stringify(check, null, 2));
|
|
309
|
+
writeWorkspaceSkillEntry(args.target);
|
|
124
310
|
if (args.smoke) runSmoke(args.target);
|
|
125
311
|
printNextSteps(args.target);
|
|
126
312
|
return;
|
|
@@ -132,7 +318,7 @@ function main() {
|
|
|
132
318
|
}
|
|
133
319
|
|
|
134
320
|
if (args.command === 'smoke') {
|
|
135
|
-
runSmoke(
|
|
321
|
+
runSmoke(SOURCE_ROOT);
|
|
136
322
|
return;
|
|
137
323
|
}
|
|
138
324
|
|
|
@@ -143,6 +329,6 @@ function main() {
|
|
|
143
329
|
try {
|
|
144
330
|
main();
|
|
145
331
|
} catch (error) {
|
|
146
|
-
console.error(`claude-measure-marker
|
|
332
|
+
console.error(`claude-measure-marker failed: ${error.message}`);
|
|
147
333
|
process.exit(1);
|
|
148
334
|
}
|
package/package.json
CHANGED