@zhuan-ai/zhuanspec 2.15.7 → 2.16.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/dist/cli/hooks.d.ts +13 -1
- package/dist/cli/hooks.js +75 -2
- package/dist/cli/index.js +111 -4
- package/dist/commands/accuracy.js +5 -1
- package/dist/commands/knowledge.d.ts +21 -0
- package/dist/commands/knowledge.js +80 -0
- package/dist/commands/progress.d.ts +78 -0
- package/dist/commands/progress.js +349 -3
- package/dist/core/archive.js +28 -0
- package/dist/core/configurators/codex.d.ts +55 -1
- package/dist/core/configurators/codex.js +234 -2
- package/dist/core/corrections/select-candidates.d.ts +58 -0
- package/dist/core/corrections/select-candidates.js +357 -0
- package/dist/core/hooks/collect-knowledge.js +80 -5
- package/dist/core/hooks/deviation-check.d.ts +26 -1
- package/dist/core/hooks/deviation-check.js +32 -184
- package/dist/core/hooks/knowledge-index.d.ts +84 -0
- package/dist/core/hooks/knowledge-index.js +270 -0
- package/dist/core/hooks/post-archive.js +25 -13
- package/dist/core/hooks/record-progress.d.ts +64 -1
- package/dist/core/hooks/record-progress.js +224 -54
- package/dist/core/hooks/summarize.js +107 -20
- package/dist/core/hooks/user-input-hook.d.ts +38 -2
- package/dist/core/hooks/user-input-hook.js +346 -21
- package/dist/core/init.d.ts +16 -0
- package/dist/core/init.js +208 -11
- package/dist/core/metrics/code-accuracy.d.ts +152 -3
- package/dist/core/metrics/code-accuracy.js +323 -19
- package/dist/core/templates/agents-root-stub.d.ts +1 -1
- package/dist/core/templates/agents-root-stub.js +1 -0
- package/dist/core/templates/agents-template.d.ts +1 -1
- package/dist/core/templates/agents-template.js +27 -25
- package/dist/core/templates/codex-hooks-template.d.ts +37 -0
- package/dist/core/templates/codex-hooks-template.js +33 -2
- package/dist/core/templates/slash-command-templates.js +208 -66
- package/dist/core/templates/tasks-template.js +66 -14
- package/dist/core/update.js +17 -0
- package/dist/core/validation/strict-rules.d.ts +28 -0
- package/dist/core/validation/strict-rules.js +284 -0
- package/dist/utils/hook-merge.d.ts +6 -0
- package/dist/utils/hook-merge.js +33 -2
- package/dist/utils/phase-utils.js +5 -0
- package/package.json +22 -20
|
@@ -13,12 +13,22 @@
|
|
|
13
13
|
*
|
|
14
14
|
* ~/.codex/ (or $CODEX_HOME)
|
|
15
15
|
* config.toml - Per-project [projects."…"] trusted_level = "trusted"
|
|
16
|
-
* block (wrapped in TRUST markers keyed by absolute path)
|
|
16
|
+
* block (wrapped in TRUST markers keyed by absolute path).
|
|
17
|
+
* Also receives [hooks.state."<abs>:<event>:<i>:<j>"]
|
|
18
|
+
* entries grafted from other already-trusted projects,
|
|
19
|
+
* wrapped in HOOK_STATE markers keyed by config.toml path.
|
|
17
20
|
*
|
|
18
21
|
* Codex does **not** load project-scoped hooks/subagents unless the project
|
|
19
22
|
* is explicitly marked trusted in the global config (see openai/codex#14601),
|
|
20
23
|
* so we opportunistically write that trust entry during `zhuanspec init`.
|
|
21
24
|
*
|
|
25
|
+
* Codex CLI >= 0.129 additionally requires each hook's content hash to be
|
|
26
|
+
* pre-approved (openai/codex#20321) or the user sees "N hooks need review".
|
|
27
|
+
* The `trusted_hash` in `[hooks.state.<key>]` depends only on normalised hook
|
|
28
|
+
* *content* (event + matcher + command), not on the key path, so we clone a
|
|
29
|
+
* previously approved hash to the current project's key to skip the manual
|
|
30
|
+
* review prompt in fresh workspaces.
|
|
31
|
+
*
|
|
22
32
|
* Idempotency:
|
|
23
33
|
* - Hooks block is regenerated inside ZHUANSPEC markers on every run.
|
|
24
34
|
* - Trust entries are keyed by absolute path; repeated runs no-op.
|
|
@@ -28,7 +38,7 @@ import path from 'path';
|
|
|
28
38
|
import os from 'os';
|
|
29
39
|
import { promises as fs } from 'fs';
|
|
30
40
|
import { FileSystemUtils } from '../../utils/file-system.js';
|
|
31
|
-
import { getCodexHooksTomlBlock } from '../templates/codex-hooks-template.js';
|
|
41
|
+
import { getCodexHooksTomlBlock, getCodexDefaultHookSlots } from '../templates/codex-hooks-template.js';
|
|
32
42
|
import { getCodexAgentTomls, } from '../templates/codex-agents-template.js';
|
|
33
43
|
import { getCodexSkillFiles } from '../templates/codex-skills-template.js';
|
|
34
44
|
import { CodexSlashCommandConfigurator } from './slash/codex.js';
|
|
@@ -51,6 +61,18 @@ function buildTrustMarkers(projectPath) {
|
|
|
51
61
|
end: `# <!-- ZHUANSPEC:TRUST:END path=${projectPath} -->`,
|
|
52
62
|
};
|
|
53
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* Build the per-path HOOK_STATE marker pair for the global config.toml.
|
|
66
|
+
* Keyed by the **project config.toml absolute path** (not the project dir)
|
|
67
|
+
* so it aligns with the prefix of each `[hooks.state."<config-path>:..."]`
|
|
68
|
+
* key that Codex records.
|
|
69
|
+
*/
|
|
70
|
+
function buildHookStateMarkers(projectConfigTomlPath) {
|
|
71
|
+
return {
|
|
72
|
+
start: `# <!-- ZHUANSPEC:HOOK_STATE:START path=${projectConfigTomlPath} -->`,
|
|
73
|
+
end: `# <!-- ZHUANSPEC:HOOK_STATE:END path=${projectConfigTomlPath} -->`,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
54
76
|
export class CodexConfigurator {
|
|
55
77
|
name = 'Codex';
|
|
56
78
|
/** Codex reads AGENTS.md for project instructions (no dedicated CODEX.md). */
|
|
@@ -80,6 +102,7 @@ export class CodexConfigurator {
|
|
|
80
102
|
const skillsWritten = await this.mirrorSkills(projectPath);
|
|
81
103
|
const configTomlPath = await this.writeProjectConfigToml(codexDir, agentFiles);
|
|
82
104
|
const { globalConfigPath, trustEntryAdded } = await this.ensureGlobalTrustEntry(projectPath);
|
|
105
|
+
const hookTrust = await this.ensureHookTrustSeed(globalConfigPath, configTomlPath);
|
|
83
106
|
const conflictingGlobalHooksJson = await this.detectGlobalHooksJsonConflict();
|
|
84
107
|
// Codex CLI >= 0.117 no longer scans `~/.codex/prompts/`; delete any
|
|
85
108
|
// ZhuanSpec-managed prompt left behind by pre-2.12.5 installs so the
|
|
@@ -94,6 +117,7 @@ export class CodexConfigurator {
|
|
|
94
117
|
trustEntryAdded,
|
|
95
118
|
conflictingGlobalHooksJson,
|
|
96
119
|
legacyPromptsCleaned,
|
|
120
|
+
hookTrust,
|
|
97
121
|
};
|
|
98
122
|
}
|
|
99
123
|
/**
|
|
@@ -292,5 +316,213 @@ export class CodexConfigurator {
|
|
|
292
316
|
const block = this.buildTrustBlock(projectPath);
|
|
293
317
|
return `${before}${block}${after}`;
|
|
294
318
|
}
|
|
319
|
+
/**
|
|
320
|
+
* Graft `[hooks.state."<key>"] trusted_hash = "sha256:..."` entries for the
|
|
321
|
+
* current project from any other project that the user has already approved
|
|
322
|
+
* via Codex's `/hooks` TUI. The grafted block is wrapped in HOOK_STATE
|
|
323
|
+
* markers keyed by `configTomlPath` so it stays idempotent across runs and
|
|
324
|
+
* coexists with HOOK_STATE blocks for sibling projects in the same file.
|
|
325
|
+
*
|
|
326
|
+
* Strategy:
|
|
327
|
+
* - Strip our own existing block first to avoid self-referencing seeds.
|
|
328
|
+
* - Scan remaining `[hooks.state."<path>:<event>:<grp>:<hdl>"]` tables;
|
|
329
|
+
* index their `trusted_hash` by the `<event>:<grp>:<hdl>` suffix so the
|
|
330
|
+
* same logical hook in any other project can serve as a seed.
|
|
331
|
+
* - For each ZhuanSpec default slot:
|
|
332
|
+
* * If a native (unmanaged) entry already owns the same key in this
|
|
333
|
+
* project, skip to avoid TOML duplicate-key errors.
|
|
334
|
+
* * Else if a seed exists, emit a managed entry pointing the user's
|
|
335
|
+
* project key at the known-good hash.
|
|
336
|
+
* * Else record the slot in `skippedNoSeed` so callers can surface a
|
|
337
|
+
* "please approve once in Codex" hint.
|
|
338
|
+
*
|
|
339
|
+
* Failures are non-fatal: any unexpected error is swallowed and reported
|
|
340
|
+
* via an empty result with `seedAvailable=false`, since hook trust is a
|
|
341
|
+
* UX optimisation — missing it just means the user sees the regular
|
|
342
|
+
* Codex review prompt.
|
|
343
|
+
*/
|
|
344
|
+
async ensureHookTrustSeed(globalConfigPath, configTomlPath) {
|
|
345
|
+
const slots = getCodexDefaultHookSlots();
|
|
346
|
+
const empty = {
|
|
347
|
+
written: [],
|
|
348
|
+
skippedNoSeed: slots.map(slotSuffix),
|
|
349
|
+
skippedNative: [],
|
|
350
|
+
seedAvailable: false,
|
|
351
|
+
};
|
|
352
|
+
try {
|
|
353
|
+
let existing = '';
|
|
354
|
+
if (await FileSystemUtils.fileExists(globalConfigPath)) {
|
|
355
|
+
existing = await FileSystemUtils.readFile(globalConfigPath);
|
|
356
|
+
}
|
|
357
|
+
// 1. Strip our own block so we don't seed from ourselves and so the
|
|
358
|
+
// "native conflict" check only sees user/legacy entries.
|
|
359
|
+
const stripped = stripHookStateBlock(existing, configTomlPath);
|
|
360
|
+
// 2. Build seed map (suffix → first hash seen anywhere except this project).
|
|
361
|
+
const allEntries = parseHookStateEntries(stripped);
|
|
362
|
+
const seedBySuffix = new Map();
|
|
363
|
+
const nativeOwnedSuffixes = new Set();
|
|
364
|
+
for (const entry of allEntries) {
|
|
365
|
+
const split = splitHookStateKey(entry.key);
|
|
366
|
+
if (!split)
|
|
367
|
+
continue;
|
|
368
|
+
if (split.pathPart === configTomlPath) {
|
|
369
|
+
// Native (unmanaged) entry already exists for this project's slot.
|
|
370
|
+
nativeOwnedSuffixes.add(split.suffix);
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
if (!seedBySuffix.has(split.suffix)) {
|
|
374
|
+
seedBySuffix.set(split.suffix, entry.hash);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
const seedAvailable = seedBySuffix.size > 0;
|
|
378
|
+
// 3. Plan per-slot writes.
|
|
379
|
+
const written = [];
|
|
380
|
+
const skippedNoSeed = [];
|
|
381
|
+
const skippedNative = [];
|
|
382
|
+
const blockEntries = [];
|
|
383
|
+
for (const slot of slots) {
|
|
384
|
+
const suffix = slotSuffix(slot);
|
|
385
|
+
if (nativeOwnedSuffixes.has(suffix)) {
|
|
386
|
+
skippedNative.push(suffix);
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
const seed = seedBySuffix.get(suffix);
|
|
390
|
+
if (!seed) {
|
|
391
|
+
skippedNoSeed.push(suffix);
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
const fullKey = `${configTomlPath}:${suffix}`;
|
|
395
|
+
blockEntries.push({ key: fullKey, hash: seed });
|
|
396
|
+
written.push(suffix);
|
|
397
|
+
}
|
|
398
|
+
// 4. Persist. When nothing to write, ensure any stale managed block is
|
|
399
|
+
// removed (e.g. user revoked all hooks elsewhere); otherwise replace.
|
|
400
|
+
const finalContent = blockEntries.length === 0
|
|
401
|
+
? stripped
|
|
402
|
+
: insertHookStateBlock(stripped, configTomlPath, buildHookStateBlockBody(configTomlPath, blockEntries));
|
|
403
|
+
if (finalContent !== existing) {
|
|
404
|
+
await FileSystemUtils.writeFile(globalConfigPath, finalContent);
|
|
405
|
+
}
|
|
406
|
+
return { written, skippedNoSeed, skippedNative, seedAvailable };
|
|
407
|
+
}
|
|
408
|
+
catch {
|
|
409
|
+
// Non-fatal: hook trust seeding is a UX optimisation.
|
|
410
|
+
return empty;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
// ---------------------------------------------------------------------------
|
|
415
|
+
// Hook trust seed helpers (module-private).
|
|
416
|
+
//
|
|
417
|
+
// These intentionally avoid pulling in a TOML parser dependency — we only
|
|
418
|
+
// need to read/write a tightly scoped subset of `~/.codex/config.toml`
|
|
419
|
+
// (`[hooks.state."..."]` tables with a single `trusted_hash` value) and
|
|
420
|
+
// must round-trip the rest of the user's file byte-for-byte.
|
|
421
|
+
// ---------------------------------------------------------------------------
|
|
422
|
+
/** `<event>:<group>:<handler>` suffix used both for seed lookup and key building. */
|
|
423
|
+
function slotSuffix(slot) {
|
|
424
|
+
return `${slot.eventSnake}:${slot.groupIndex}:${slot.handlerIndex}`;
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Parse `[hooks.state."<key>"] trusted_hash = "..."` entries from a TOML
|
|
428
|
+
* blob using a line walker (no full TOML grammar needed).
|
|
429
|
+
*/
|
|
430
|
+
function parseHookStateEntries(content) {
|
|
431
|
+
const lines = content.split('\n');
|
|
432
|
+
const entries = [];
|
|
433
|
+
const headerRe = /^\[hooks\.state\."((?:[^"\\]|\\.)+)"\]\s*$/;
|
|
434
|
+
const hashRe = /^trusted_hash\s*=\s*"([^"]+)"\s*$/;
|
|
435
|
+
for (let i = 0; i < lines.length; i++) {
|
|
436
|
+
const m = lines[i].match(headerRe);
|
|
437
|
+
if (!m)
|
|
438
|
+
continue;
|
|
439
|
+
const rawKey = m[1];
|
|
440
|
+
// Walk forward until the next table header or EOF, capturing trusted_hash.
|
|
441
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
442
|
+
const trimmed = lines[j].trim();
|
|
443
|
+
if (trimmed === '' || trimmed.startsWith('#'))
|
|
444
|
+
continue;
|
|
445
|
+
if (trimmed.startsWith('['))
|
|
446
|
+
break;
|
|
447
|
+
const h = trimmed.match(hashRe);
|
|
448
|
+
if (h) {
|
|
449
|
+
entries.push({ key: unescapeTomlBasic(rawKey), hash: h[1] });
|
|
450
|
+
break;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
return entries;
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Split a hook-state key (`<config-toml-path>:<event>:<grp>:<hdl>`) into its
|
|
458
|
+
* path prefix and 3-field suffix. Handles paths containing `:` (Windows
|
|
459
|
+
* drives, etc.) by splitting on the **last** three colons.
|
|
460
|
+
*/
|
|
461
|
+
function splitHookStateKey(key) {
|
|
462
|
+
const parts = key.split(':');
|
|
463
|
+
if (parts.length < 4)
|
|
464
|
+
return null;
|
|
465
|
+
const suffix = parts.slice(-3).join(':');
|
|
466
|
+
const pathPart = parts.slice(0, parts.length - 3).join(':');
|
|
467
|
+
if (!pathPart)
|
|
468
|
+
return null;
|
|
469
|
+
return { pathPart, suffix };
|
|
470
|
+
}
|
|
471
|
+
/** Reverse TOML basic-string escapes for `\\\\` and `\\"` (sufficient for paths). */
|
|
472
|
+
function unescapeTomlBasic(raw) {
|
|
473
|
+
return raw.replace(/\\\\/g, '\\').replace(/\\"/g, '"');
|
|
474
|
+
}
|
|
475
|
+
/** Forward TOML basic-string escape — mirrors `escapeBasic` used elsewhere. */
|
|
476
|
+
function escapeTomlBasic(raw) {
|
|
477
|
+
return raw.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
478
|
+
}
|
|
479
|
+
/** Build the body (between markers) of the HOOK_STATE managed block. */
|
|
480
|
+
function buildHookStateBlockBody(configTomlPath, entries) {
|
|
481
|
+
const { start, end } = buildHookStateMarkers(configTomlPath);
|
|
482
|
+
const tables = entries.map((e) => [`[hooks.state."${escapeTomlBasic(e.key)}"]`, `trusted_hash = "${e.hash}"`].join('\n'));
|
|
483
|
+
const header = [
|
|
484
|
+
'# ZhuanSpec hook trust seeds — grafted from previously approved projects.',
|
|
485
|
+
'# Managed block; regenerated by `zhuanspec init/update`. Do not edit by hand.',
|
|
486
|
+
].join('\n');
|
|
487
|
+
return [start, header, '', tables.join('\n\n'), end].join('\n');
|
|
488
|
+
}
|
|
489
|
+
/** Remove our marker-wrapped HOOK_STATE block for `configTomlPath` from `content`. */
|
|
490
|
+
function stripHookStateBlock(content, configTomlPath) {
|
|
491
|
+
const { start, end } = buildHookStateMarkers(configTomlPath);
|
|
492
|
+
const startIdx = content.indexOf(start);
|
|
493
|
+
if (startIdx === -1)
|
|
494
|
+
return content;
|
|
495
|
+
const endIdx = content.indexOf(end, startIdx + start.length);
|
|
496
|
+
if (endIdx === -1)
|
|
497
|
+
return content;
|
|
498
|
+
const blockStart = startIdx;
|
|
499
|
+
let blockEnd = endIdx + end.length;
|
|
500
|
+
// Consume the trailing newline written together with the block so we
|
|
501
|
+
// don't leave an orphan blank line behind. Leave any leading newline
|
|
502
|
+
// intact so `insertHookStateBlock` produces a stable, idempotent layout.
|
|
503
|
+
if (content[blockEnd] === '\n')
|
|
504
|
+
blockEnd += 1;
|
|
505
|
+
return content.substring(0, blockStart) + content.substring(blockEnd);
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Insert (or replace) the HOOK_STATE managed block for `configTomlPath` in
|
|
509
|
+
* `content`. When a previous block exists, it's swapped in-place to keep the
|
|
510
|
+
* file order stable. Otherwise the block is appended to EOF; we adjust the
|
|
511
|
+
* separator so a follow-up `strip + insert` round-trip produces byte-for-byte
|
|
512
|
+
* the same result (idempotency).
|
|
513
|
+
*/
|
|
514
|
+
function insertHookStateBlock(content, configTomlPath, block) {
|
|
515
|
+
const { start, end } = buildHookStateMarkers(configTomlPath);
|
|
516
|
+
const startIdx = content.indexOf(start);
|
|
517
|
+
if (startIdx !== -1) {
|
|
518
|
+
const endIdx = content.indexOf(end, startIdx + start.length);
|
|
519
|
+
if (endIdx !== -1) {
|
|
520
|
+
const before = content.substring(0, startIdx);
|
|
521
|
+
const after = content.substring(endIdx + end.length);
|
|
522
|
+
return `${before}${block}${after}`;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
const separator = content.length === 0 || content.endsWith('\n') ? '' : '\n';
|
|
526
|
+
return `${content}${separator}${block}\n`;
|
|
295
527
|
}
|
|
296
528
|
//# sourceMappingURL=codex.js.map
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pending corrections candidate selection.
|
|
3
|
+
*
|
|
4
|
+
* 责任:把 `zhuanspec/changes/<id>/metrics/user_inputs.json` 里的纠偏 prompt 加工成
|
|
5
|
+
* "可读、去毛刺、去重、带建议分类" 的候选清单,给 review 阶段的踩坑沉淀决策使用。
|
|
6
|
+
*
|
|
7
|
+
* 不写文件、不改 progress.json,只做"读 + 计算 + 返回结构"。
|
|
8
|
+
*/
|
|
9
|
+
export type CandidateCategory = 'troubleshooting' | 'best-practices' | 'implicit-conventions';
|
|
10
|
+
export type CandidateSourceLevel = 1 | 2;
|
|
11
|
+
export interface CorrectionCandidate {
|
|
12
|
+
/** 1-based 序号,对应"用户回复编号清单"的编号 */
|
|
13
|
+
index: number;
|
|
14
|
+
/** 关联的 user_inputs.json.inputId */
|
|
15
|
+
inputId?: string;
|
|
16
|
+
timestamp?: string;
|
|
17
|
+
phase?: string;
|
|
18
|
+
/** 优先 fullPrompt,没有就 summary,再没有就空字符串 */
|
|
19
|
+
text: string;
|
|
20
|
+
/** 截断展示用的预览(≤ 200 字) */
|
|
21
|
+
preview: string;
|
|
22
|
+
/** 命中的纠偏关键词(取 USER_CORRECTION_KEYWORDS / REQUIREMENT_CHANGE_KEYWORDS) */
|
|
23
|
+
matchedKeywords: string[];
|
|
24
|
+
isRequirementChange: boolean;
|
|
25
|
+
/** 启发式建议分类(用户/skill 仍可改) */
|
|
26
|
+
suggestedCategory: CandidateCategory;
|
|
27
|
+
/** 拟用文件名(不含目录前缀) */
|
|
28
|
+
suggestedFilename: string;
|
|
29
|
+
}
|
|
30
|
+
export interface DroppedCandidate {
|
|
31
|
+
/** drop 后保留的原始序号(按出现顺序,1-based)——用于在 markdown 里展示 */
|
|
32
|
+
rawIndex: number;
|
|
33
|
+
inputId?: string;
|
|
34
|
+
timestamp?: string;
|
|
35
|
+
reason: 'empty' | 'trivial-acknowledgement' | 'too-short-no-keyword' | 'duplicate-of' | 'subset-of';
|
|
36
|
+
/** 当 reason 为 duplicate-of / subset-of 时,指向被引用的有效候选编号 */
|
|
37
|
+
refIndex?: number;
|
|
38
|
+
preview: string;
|
|
39
|
+
}
|
|
40
|
+
export interface CandidateSelectionResult {
|
|
41
|
+
changeId: string;
|
|
42
|
+
/** 数据源层级(1: user_inputs 带 correctionSignal;2: 关键词推断) */
|
|
43
|
+
level: CandidateSourceLevel | null;
|
|
44
|
+
totalRaw: number;
|
|
45
|
+
candidates: CorrectionCandidate[];
|
|
46
|
+
dropped: DroppedCandidate[];
|
|
47
|
+
/** 数据完整性提示——读不到 user_inputs.json 时给空候选 + 错误说明 */
|
|
48
|
+
notice?: string;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* 从 change 目录抽取并加工纠偏候选。
|
|
52
|
+
*/
|
|
53
|
+
export declare function selectCorrectionCandidates(cwd: string, changeId: string): Promise<CandidateSelectionResult>;
|
|
54
|
+
/**
|
|
55
|
+
* 把候选结果渲染成 markdown,用于落到 review/pending-corrections.md。
|
|
56
|
+
*/
|
|
57
|
+
export declare function renderPendingCorrectionsMarkdown(result: CandidateSelectionResult): string;
|
|
58
|
+
//# sourceMappingURL=select-candidates.d.ts.map
|