@zhuan-ai/zhuanspec 2.16.4 → 2.16.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -405,6 +405,55 @@ function generateProposalChangeSummary(toolInput) {
|
|
|
405
405
|
// Truncate to 120 chars
|
|
406
406
|
return firstLine.length > 120 ? firstLine.substring(0, 117) + '...' : firstLine;
|
|
407
407
|
}
|
|
408
|
+
function parseApplyPatch(command) {
|
|
409
|
+
const result = { files: [], totalLinesAdded: 0, totalLinesRemoved: 0 };
|
|
410
|
+
if (!command)
|
|
411
|
+
return result;
|
|
412
|
+
const lines = command.split('\n');
|
|
413
|
+
let currentFile = null;
|
|
414
|
+
for (const line of lines) {
|
|
415
|
+
if (line.startsWith('*** Add File: ') || line.startsWith('*** Update File: ')) {
|
|
416
|
+
if (currentFile)
|
|
417
|
+
result.files.push(currentFile);
|
|
418
|
+
const filePath = line.replace(/^\*\*\* (?:Add|Update) File:\s*/, '').trim();
|
|
419
|
+
currentFile = { path: filePath, linesAdded: 0, linesRemoved: 0 };
|
|
420
|
+
}
|
|
421
|
+
else if (line.startsWith('*** Delete File: ')) {
|
|
422
|
+
if (currentFile)
|
|
423
|
+
result.files.push(currentFile);
|
|
424
|
+
const filePath = line.replace(/^\*\*\* Delete File:\s*/, '').trim();
|
|
425
|
+
currentFile = { path: filePath, linesAdded: 0, linesRemoved: 0 };
|
|
426
|
+
}
|
|
427
|
+
else if (line === '*** End Patch' || line === '*** Begin Patch') {
|
|
428
|
+
if (currentFile) {
|
|
429
|
+
result.files.push(currentFile);
|
|
430
|
+
currentFile = null;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
else if (line.startsWith('*** ')) {
|
|
434
|
+
// Other patch directives (e.g., *** Rename File:) — push current and skip
|
|
435
|
+
if (currentFile) {
|
|
436
|
+
result.files.push(currentFile);
|
|
437
|
+
currentFile = null;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
else if (currentFile) {
|
|
441
|
+
if (line.startsWith('+'))
|
|
442
|
+
currentFile.linesAdded++;
|
|
443
|
+
else if (line.startsWith('-'))
|
|
444
|
+
currentFile.linesRemoved++;
|
|
445
|
+
// Context lines (space prefix or @@ markers) are ignored for counting
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
if (currentFile)
|
|
449
|
+
result.files.push(currentFile);
|
|
450
|
+
// Calculate totals
|
|
451
|
+
for (const f of result.files) {
|
|
452
|
+
result.totalLinesAdded += f.linesAdded;
|
|
453
|
+
result.totalLinesRemoved += f.linesRemoved;
|
|
454
|
+
}
|
|
455
|
+
return result;
|
|
456
|
+
}
|
|
408
457
|
async function runRecordProgress(filePath, toolName, success, stdinData) {
|
|
409
458
|
const cwd = resolveZhuanSpecRoot();
|
|
410
459
|
// Priority: filePath-derived > progress.json scan > environment variables
|
|
@@ -620,8 +669,31 @@ async function runRecordProgress(filePath, toolName, success, stdinData) {
|
|
|
620
669
|
progress.currentNode = process.env.ZHUANSPEC_CURRENT_NODE || phase;
|
|
621
670
|
progress.currentTask = currentTask || progress.currentTask;
|
|
622
671
|
progress.toolCalls.push(toolCall);
|
|
623
|
-
|
|
624
|
-
|
|
672
|
+
// === Codex apply_patch 支持 ===
|
|
673
|
+
// Codex 使用 apply_patch 工具,file_path 为空,实际内容在 command 字段的 patch 格式中
|
|
674
|
+
let patchResult = null;
|
|
675
|
+
if (toolName === 'apply_patch') {
|
|
676
|
+
const command = stdinData.tool_input?.command || '';
|
|
677
|
+
patchResult = parseApplyPatch(command);
|
|
678
|
+
// 用第一个文件路径作为 filePath(用于 accuracy 追踪和 change detection)
|
|
679
|
+
if (patchResult.files.length > 0 && !filePath) {
|
|
680
|
+
filePath = patchResult.files[0].path;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
const WRITE_TOOLS = new Set(['Write', 'Edit', 'MultiEdit', 'NotebookEdit', 'apply_patch']);
|
|
684
|
+
if (toolName === 'apply_patch' && patchResult) {
|
|
685
|
+
// apply_patch 可修改多文件,逐一加入 filesModified
|
|
686
|
+
const zhuanspecDirPrefix = path.resolve(process.cwd(), 'zhuanspec') + path.sep;
|
|
687
|
+
for (const f of patchResult.files) {
|
|
688
|
+
if (!f.path || progress.filesModified.includes(f.path))
|
|
689
|
+
continue;
|
|
690
|
+
const absF = path.isAbsolute(f.path) ? f.path : path.resolve(process.cwd(), f.path);
|
|
691
|
+
if (!absF.startsWith(zhuanspecDirPrefix)) {
|
|
692
|
+
progress.filesModified.push(f.path);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
else if (filePath && WRITE_TOOLS.has(toolName) && !progress.filesModified.includes(filePath)) {
|
|
625
697
|
// Exclude ZhuanSpec internal files (spec/metrics/doc files inside zhuanspec/ dir)
|
|
626
698
|
const absFilePath = path.isAbsolute(filePath) ? filePath : path.resolve(process.cwd(), filePath);
|
|
627
699
|
const zhuanspecDirPrefix = path.resolve(process.cwd(), 'zhuanspec') + path.sep;
|
|
@@ -631,7 +703,7 @@ async function runRecordProgress(filePath, toolName, success, stdinData) {
|
|
|
631
703
|
}
|
|
632
704
|
// Detect proposal file changes and record them
|
|
633
705
|
const changePath = path.join(zhuanspecDir, 'changes', changeId);
|
|
634
|
-
const WRITE_EDIT_TOOLS = new Set(['Write', 'Edit', 'MultiEdit']);
|
|
706
|
+
const WRITE_EDIT_TOOLS = new Set(['Write', 'Edit', 'MultiEdit', 'apply_patch']);
|
|
635
707
|
if (filePath && WRITE_EDIT_TOOLS.has(toolName) && isProposalFile(filePath, changePath)) {
|
|
636
708
|
const relativeProposalPath = path.relative(changePath, path.isAbsolute(filePath) ? filePath : path.resolve(cwd, filePath));
|
|
637
709
|
// 提案文件变更行数:按工具类型取不同字段,避免 Edit/MultiEdit 全记为 0
|
|
@@ -652,6 +724,16 @@ async function runRecordProgress(filePath, toolName, success, stdinData) {
|
|
|
652
724
|
contentLines += Math.max(ns, os);
|
|
653
725
|
}
|
|
654
726
|
}
|
|
727
|
+
else if (toolName === 'apply_patch' && patchResult) {
|
|
728
|
+
// apply_patch 的提案文件变更行数:用 patch 中涉及该提案文件的行数
|
|
729
|
+
const absChangePath = path.isAbsolute(changePath) ? changePath : path.resolve(cwd, changePath);
|
|
730
|
+
for (const f of patchResult.files) {
|
|
731
|
+
const absF = path.isAbsolute(f.path) ? f.path : path.resolve(cwd, f.path);
|
|
732
|
+
if (absF.startsWith(absChangePath + path.sep)) {
|
|
733
|
+
contentLines += f.linesAdded + f.linesRemoved;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
}
|
|
655
737
|
const record = {
|
|
656
738
|
changeRecordId: `pc-${Date.now()}`,
|
|
657
739
|
timestamp,
|
|
@@ -725,6 +807,11 @@ async function runRecordProgress(filePath, toolName, success, stdinData) {
|
|
|
725
807
|
}
|
|
726
808
|
contentSource = toolInputEdits.length > 0 ? 'edits[]' : 'none';
|
|
727
809
|
}
|
|
810
|
+
else if (toolName === 'apply_patch' && patchResult) {
|
|
811
|
+
// Codex apply_patch:统计 patch 中所有 +/- 行
|
|
812
|
+
changeLines = patchResult.totalLinesAdded + patchResult.totalLinesRemoved;
|
|
813
|
+
contentSource = patchResult.files.length > 0 ? 'command' : 'none';
|
|
814
|
+
}
|
|
728
815
|
let estimatedLines = 0;
|
|
729
816
|
// === P0 兜底:post-baseline-fallback ===
|
|
730
817
|
// 正常链路:UserPromptSubmit 的 user-input hook 会在 phaseBaseline 已打标时写入
|
|
@@ -185,17 +185,19 @@ export async function userInputHook(content, responseTimeMs) {
|
|
|
185
185
|
logDebug(cwd, { event: 'skip', reason: 'content-too-short', changeId, phase, phaseSource, contentLen });
|
|
186
186
|
return { continue: true };
|
|
187
187
|
}
|
|
188
|
-
// Skip slash/bang commands
|
|
189
|
-
//
|
|
188
|
+
// Skip slash/bang/dollar commands:
|
|
189
|
+
// Claude Code: /zhuanspec:proposal, /compact, /clear, !ls
|
|
190
|
+
// Codex: $zhuanspec-proposal, $zhuanspec-techDesign 等 skill 触发
|
|
191
|
+
// 这类 prompt 是命令/workflow 触发,非"用户对 AI 产出的纠偏/澄清/补充",
|
|
190
192
|
// 不应计入 user_inputs.json,更不应放进纠偏溯源池影响 accuracyRate。
|
|
191
193
|
// 注意:Claude Code 在 slash 命令前会注入 NAK(U+0015) 等 C0 控制字符做标记,
|
|
192
194
|
// 普通 trim() 不会剥控制字符,必须显式剥掉前缀的 C0/DEL 再做正则判定,
|
|
193
195
|
// 否则像 "\u0015/zhuanspec:proposal" 这种命令会漏过过滤被记成普通用户输入。
|
|
194
196
|
const trimmedContent = content.trim().replace(/^[\x00-\x1F\x7F]+/, '');
|
|
195
|
-
if (/^[
|
|
197
|
+
if (/^[/!$][A-Za-z0-9_:/@.\-]+(\s|$)/.test(trimmedContent) || /^\/[\w:\-]+$/.test(trimmedContent)) {
|
|
196
198
|
logDebug(cwd, {
|
|
197
199
|
event: 'skip',
|
|
198
|
-
reason: '
|
|
200
|
+
reason: 'skill-command',
|
|
199
201
|
changeId,
|
|
200
202
|
phase,
|
|
201
203
|
phaseSource,
|
|
@@ -50,7 +50,7 @@ const CODEX_DEFAULT_HOOKS = [
|
|
|
50
50
|
},
|
|
51
51
|
{
|
|
52
52
|
event: 'PostToolUse',
|
|
53
|
-
matcher: 'Write|Edit|Read|Grep|Glob|Bash|Skill|Agent|mcp__.*',
|
|
53
|
+
matcher: 'Write|Edit|apply_patch|Read|Grep|Glob|Bash|Skill|Agent|mcp__.*',
|
|
54
54
|
command: 'ZHUANSPEC_HOST=codex zhuanspec-hook record-progress --json',
|
|
55
55
|
statusMessage: '📝 Recording progress...',
|
|
56
56
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhuan-ai/zhuanspec",
|
|
3
|
-
"version": "2.16.
|
|
3
|
+
"version": "2.16.5",
|
|
4
4
|
"description": "AI-native system for spec-driven development",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"zhuanspec",
|
|
@@ -39,6 +39,26 @@
|
|
|
39
39
|
"!dist/**/__tests__",
|
|
40
40
|
"!dist/**/*.map"
|
|
41
41
|
],
|
|
42
|
+
"scripts": {
|
|
43
|
+
"lint": "eslint src/",
|
|
44
|
+
"build": "node build.js",
|
|
45
|
+
"dev": "tsc --watch",
|
|
46
|
+
"dev:cli": "pnpm build && node bin/zhuanspec.js",
|
|
47
|
+
"test": "vitest run",
|
|
48
|
+
"test:watch": "vitest",
|
|
49
|
+
"test:ui": "vitest --ui",
|
|
50
|
+
"test:coverage": "vitest --coverage",
|
|
51
|
+
"test:postinstall": "node scripts/postinstall.js",
|
|
52
|
+
"prepare": "npm run build",
|
|
53
|
+
"prepublishOnly": "npm run build",
|
|
54
|
+
"postinstall": "node scripts/postinstall.js",
|
|
55
|
+
"check:pack-version": "node scripts/pack-version-check.mjs",
|
|
56
|
+
"diagnose:cursor": "node scripts/diagnose-cursor-commands.js",
|
|
57
|
+
"release": "pnpm run release:ci",
|
|
58
|
+
"release:ci": "pnpm run check:pack-version && pnpm exec changeset publish",
|
|
59
|
+
"release:local": "pnpm exec changeset version && pnpm run check:pack-version && pnpm exec changeset publish",
|
|
60
|
+
"changeset": "changeset"
|
|
61
|
+
},
|
|
42
62
|
"engines": {
|
|
43
63
|
"node": ">=20.19.0"
|
|
44
64
|
},
|
|
@@ -60,23 +80,5 @@
|
|
|
60
80
|
"ora": "^8.2.0",
|
|
61
81
|
"yaml": "^2.8.2",
|
|
62
82
|
"zod": "^4.0.17"
|
|
63
|
-
},
|
|
64
|
-
"scripts": {
|
|
65
|
-
"lint": "eslint src/",
|
|
66
|
-
"build": "node build.js",
|
|
67
|
-
"dev": "tsc --watch",
|
|
68
|
-
"dev:cli": "pnpm build && node bin/zhuanspec.js",
|
|
69
|
-
"test": "vitest run",
|
|
70
|
-
"test:watch": "vitest",
|
|
71
|
-
"test:ui": "vitest --ui",
|
|
72
|
-
"test:coverage": "vitest --coverage",
|
|
73
|
-
"test:postinstall": "node scripts/postinstall.js",
|
|
74
|
-
"postinstall": "node scripts/postinstall.js",
|
|
75
|
-
"check:pack-version": "node scripts/pack-version-check.mjs",
|
|
76
|
-
"diagnose:cursor": "node scripts/diagnose-cursor-commands.js",
|
|
77
|
-
"release": "pnpm run release:ci",
|
|
78
|
-
"release:ci": "pnpm run check:pack-version && pnpm exec changeset publish",
|
|
79
|
-
"release:local": "pnpm exec changeset version && pnpm run check:pack-version && pnpm exec changeset publish",
|
|
80
|
-
"changeset": "changeset"
|
|
81
83
|
}
|
|
82
|
-
}
|
|
84
|
+
}
|