aodw-skill 0.7.24 → 0.7.25
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 +27 -0
- package/bin/aodw.js +23 -0
- package/bin/commands/install-guard-hook.js +69 -0
- package/bin/commands/trace-guard.js +156 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -113,6 +113,33 @@ npx aodw-skill new
|
|
|
113
113
|
|
|
114
114
|
交互式创建新的开发任务 Ticket。
|
|
115
115
|
|
|
116
|
+
### AODW v0 防漏记(推荐)
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
npx aodw-skill enable-guard-hook
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
默认行为(更自动):
|
|
123
|
+
- `npx aodw-skill init` 时会自动尝试安装 guard hook(若无自定义 hook 冲突)
|
|
124
|
+
- 提交时自动执行 `guard --auto-fix --stage-audit`
|
|
125
|
+
- 若 `latest` 版本尚未包含 `guard` 命令,hook 会自动回退尝试 `aodw-skill@beta`
|
|
126
|
+
|
|
127
|
+
在当前项目安装 `pre-commit` guard,自动处理:
|
|
128
|
+
- 改了代码目录(如 `cli/`、`templates/`、`src/` 等)
|
|
129
|
+
- 但没有任何流程痕迹更新(如 `RT/`、`maintainers/`、`.aodw-next/06-project/`)
|
|
130
|
+
- 自动生成并暂存 `.aodw-next/06-project/audit-latest.md` 作为最小补录
|
|
131
|
+
|
|
132
|
+
手动命令:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
npx aodw-skill guard
|
|
136
|
+
npx aodw-skill audit --write
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
用途:
|
|
140
|
+
- `guard`: 提交前检查是否“有改动无痕迹”
|
|
141
|
+
- `audit --write`: 生成最小补录草稿到 `.aodw-next/06-project/audit-latest.md`
|
|
142
|
+
|
|
116
143
|
## 维护者发布流程
|
|
117
144
|
|
|
118
145
|
后续版本发布统一采用以下方式:
|
package/bin/aodw.js
CHANGED
|
@@ -18,6 +18,8 @@ import {
|
|
|
18
18
|
import { serve } from './commands/serve.js';
|
|
19
19
|
import { createNewRT } from './commands/new.js';
|
|
20
20
|
import { initTools } from './commands/init-tools.js';
|
|
21
|
+
import { guardDocTrace, auditDocTrace } from './commands/trace-guard.js';
|
|
22
|
+
import { enableGuardHook } from './commands/install-guard-hook.js';
|
|
21
23
|
import { saveProjectConfig, saveUserConfig, getProjectConfig, getUserConfig } from './utils/config.js';
|
|
22
24
|
import {
|
|
23
25
|
detectTechStack,
|
|
@@ -408,6 +410,9 @@ async function runInit() {
|
|
|
408
410
|
}
|
|
409
411
|
}
|
|
410
412
|
|
|
413
|
+
// 4. Enable v0 guard hook automatically in user project
|
|
414
|
+
await enableGuardHook({ silent: true });
|
|
415
|
+
|
|
411
416
|
console.log(chalk.green('\n✅ AODW-Next 初始化成功!'));
|
|
412
417
|
console.log(chalk.white(`项目: ${projectName}`));
|
|
413
418
|
|
|
@@ -690,6 +695,24 @@ program
|
|
|
690
695
|
.description('Initialize development tools (ESLint, Prettier, Ruff, Black, etc.)')
|
|
691
696
|
.action(initTools);
|
|
692
697
|
|
|
698
|
+
program
|
|
699
|
+
.command('guard')
|
|
700
|
+
.description('v0 guard: block code changes without trace updates')
|
|
701
|
+
.option('--auto-fix', 'Auto-generate minimal audit trace when missing')
|
|
702
|
+
.option('--stage-audit', 'Stage generated audit trace file automatically')
|
|
703
|
+
.action(guardDocTrace);
|
|
704
|
+
|
|
705
|
+
program
|
|
706
|
+
.command('audit')
|
|
707
|
+
.description('Generate a minimal AODW audit draft from staged changes')
|
|
708
|
+
.option('--write', 'Write draft into .aodw-next/06-project/audit-latest.md')
|
|
709
|
+
.action(auditDocTrace);
|
|
710
|
+
|
|
711
|
+
program
|
|
712
|
+
.command('enable-guard-hook')
|
|
713
|
+
.description('Install pre-commit hook to enforce AODW v0 trace guard')
|
|
714
|
+
.action(enableGuardHook);
|
|
715
|
+
|
|
693
716
|
|
|
694
717
|
// Main Entry Point
|
|
695
718
|
if (!process.argv.slice(2).length) {
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
|
|
5
|
+
function getHookPath() {
|
|
6
|
+
return path.join(process.cwd(), '.git', 'hooks', 'pre-commit');
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function getHookContent() {
|
|
10
|
+
return `#!/usr/bin/env sh
|
|
11
|
+
# AODW v0 trace guard hook
|
|
12
|
+
# Auto-generated by aodw-skill enable-guard-hook
|
|
13
|
+
|
|
14
|
+
if [ -x "./node_modules/.bin/aodw-skill" ]; then
|
|
15
|
+
./node_modules/.bin/aodw-skill guard --auto-fix --stage-audit
|
|
16
|
+
exit $?
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
# Fallback strategy:
|
|
20
|
+
# 1) Try latest installed/resolved aodw-skill
|
|
21
|
+
# 2) If command not found in that version, fallback to beta channel
|
|
22
|
+
npx --yes aodw-skill guard --auto-fix --stage-audit && exit 0
|
|
23
|
+
npx --yes aodw-skill@beta guard --auto-fix --stage-audit
|
|
24
|
+
exit $?
|
|
25
|
+
`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function enableGuardHook(options = {}) {
|
|
29
|
+
const { silent = false } = options;
|
|
30
|
+
const hookPath = getHookPath();
|
|
31
|
+
const gitDir = path.join(process.cwd(), '.git');
|
|
32
|
+
|
|
33
|
+
if (!fs.existsSync(gitDir)) {
|
|
34
|
+
if (!silent) {
|
|
35
|
+
console.log(chalk.red('❌ 当前目录不是 git 仓库,无法安装 pre-commit hook。'));
|
|
36
|
+
}
|
|
37
|
+
process.exitCode = 1;
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
await fs.ensureDir(path.dirname(hookPath));
|
|
42
|
+
|
|
43
|
+
let shouldOverwrite = true;
|
|
44
|
+
if (fs.existsSync(hookPath)) {
|
|
45
|
+
const existing = await fs.readFile(hookPath, 'utf8');
|
|
46
|
+
if (!existing.includes('AODW v0 trace guard hook')) {
|
|
47
|
+
if (!silent) {
|
|
48
|
+
console.log(chalk.yellow('⚠️ 检测到已有 pre-commit hook。'));
|
|
49
|
+
console.log(chalk.yellow(' 为避免覆盖你的自定义逻辑,本次不自动改写。'));
|
|
50
|
+
console.log(chalk.cyan(' 建议手工在现有 hook 中加入: aodw-skill guard --auto-fix --stage-audit'));
|
|
51
|
+
}
|
|
52
|
+
shouldOverwrite = false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (!shouldOverwrite) {
|
|
57
|
+
process.exitCode = 1;
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
await fs.writeFile(hookPath, getHookContent(), 'utf8');
|
|
62
|
+
await fs.chmod(hookPath, 0o755);
|
|
63
|
+
|
|
64
|
+
if (!silent) {
|
|
65
|
+
console.log(chalk.green('✅ 已启用 AODW v0 guard pre-commit hook。'));
|
|
66
|
+
console.log(chalk.gray(' 提交时将自动检查并自动补录最小流程痕迹。'));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { execSync } from 'child_process';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
|
|
6
|
+
const CORE_DIR = process.env.AODW_CORE_DIR || '.aodw-next';
|
|
7
|
+
|
|
8
|
+
function getStagedFiles() {
|
|
9
|
+
try {
|
|
10
|
+
const output = execSync('git diff --cached --name-only --diff-filter=ACMR', {
|
|
11
|
+
encoding: 'utf8',
|
|
12
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
13
|
+
});
|
|
14
|
+
return output
|
|
15
|
+
.split('\n')
|
|
16
|
+
.map((line) => line.trim())
|
|
17
|
+
.filter(Boolean);
|
|
18
|
+
} catch (error) {
|
|
19
|
+
throw new Error('无法读取 staged 文件,请确认当前目录是 git 仓库并且 git 可用。');
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function isCodePath(filePath) {
|
|
24
|
+
const normalized = filePath.replace(/\\/g, '/');
|
|
25
|
+
|
|
26
|
+
// v0: 先覆盖最关键目录,避免规则过重
|
|
27
|
+
if (normalized.startsWith('cli/') || normalized.startsWith('templates/')) {
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// 面向用户项目的常见代码目录(保持宽松,不做复杂识别)
|
|
32
|
+
const commonCodeDirs = ['src/', 'app/', 'backend/', 'frontend/', 'api/'];
|
|
33
|
+
return commonCodeDirs.some((dir) => normalized.startsWith(dir));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function isTracePath(filePath) {
|
|
37
|
+
const normalized = filePath.replace(/\\/g, '/');
|
|
38
|
+
|
|
39
|
+
const tracePrefixes = [
|
|
40
|
+
'RT/',
|
|
41
|
+
'maintainers/',
|
|
42
|
+
`${CORE_DIR}/06-project/`,
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
if (tracePrefixes.some((prefix) => normalized.startsWith(prefix))) {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const traceFileNames = ['rt-lite.md', 'meta.yaml', 'changelog.md', 'tests.md'];
|
|
50
|
+
return traceFileNames.some((name) => normalized.endsWith(`/${name}`) || normalized === name);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function splitFiles(stagedFiles) {
|
|
54
|
+
const codeFiles = stagedFiles.filter(isCodePath);
|
|
55
|
+
const traceFiles = stagedFiles.filter(isTracePath);
|
|
56
|
+
const otherFiles = stagedFiles.filter((filePath) => !codeFiles.includes(filePath) && !traceFiles.includes(filePath));
|
|
57
|
+
|
|
58
|
+
return { codeFiles, traceFiles, otherFiles };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function writeAuditDraft(stagedFiles, splitResult) {
|
|
62
|
+
const { codeFiles, traceFiles, otherFiles } = splitResult;
|
|
63
|
+
const now = new Date().toISOString();
|
|
64
|
+
const lines = [
|
|
65
|
+
'# AODW Audit Draft',
|
|
66
|
+
'',
|
|
67
|
+
`- generated_at: ${now}`,
|
|
68
|
+
`- staged_files_count: ${stagedFiles.length}`,
|
|
69
|
+
'',
|
|
70
|
+
'## 1) Code Changes',
|
|
71
|
+
...(codeFiles.length > 0 ? codeFiles.map((f) => `- ${f}`) : ['- (none)']),
|
|
72
|
+
'',
|
|
73
|
+
'## 2) Trace Updates',
|
|
74
|
+
...(traceFiles.length > 0 ? traceFiles.map((f) => `- ${f}`) : ['- (none)']),
|
|
75
|
+
'',
|
|
76
|
+
'## 3) Other Changes',
|
|
77
|
+
...(otherFiles.length > 0 ? otherFiles.map((f) => `- ${f}`) : ['- (none)']),
|
|
78
|
+
'',
|
|
79
|
+
'## 4) Fill-in Checklist',
|
|
80
|
+
'- 改了什么(1-3 行):',
|
|
81
|
+
'- 影响范围(模块/目录):',
|
|
82
|
+
'- 验证方式(最小步骤):',
|
|
83
|
+
'',
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
const outputPath = path.join(process.cwd(), CORE_DIR, '06-project', 'audit-latest.md');
|
|
87
|
+
fs.ensureDirSync(path.dirname(outputPath));
|
|
88
|
+
fs.writeFileSync(outputPath, `${lines.join('\n')}\n`, 'utf8');
|
|
89
|
+
return outputPath;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function stageFile(filePath) {
|
|
93
|
+
const relativePath = path.relative(process.cwd(), filePath);
|
|
94
|
+
execSync(`git add "${relativePath}"`, { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function guardDocTrace(options = {}) {
|
|
98
|
+
const { autoFix = false, stageAudit = false } = options;
|
|
99
|
+
const stagedFiles = getStagedFiles();
|
|
100
|
+
const splitResult = splitFiles(stagedFiles);
|
|
101
|
+
const { codeFiles, traceFiles } = splitResult;
|
|
102
|
+
|
|
103
|
+
if (codeFiles.length === 0) {
|
|
104
|
+
console.log(chalk.green('✅ guard 通过:未检测到代码目录改动。'));
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (traceFiles.length > 0) {
|
|
109
|
+
console.log(chalk.green('✅ guard 通过:检测到流程痕迹更新。'));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (autoFix) {
|
|
114
|
+
try {
|
|
115
|
+
const outputPath = writeAuditDraft(stagedFiles, splitResult);
|
|
116
|
+
if (stageAudit) {
|
|
117
|
+
stageFile(outputPath);
|
|
118
|
+
}
|
|
119
|
+
console.log(chalk.yellow('⚠️ guard 自动补录:检测到代码改动但无流程痕迹。'));
|
|
120
|
+
console.log(chalk.green(`✅ 已自动生成${stageAudit ? '并暂存' : ''}补录文件: ${path.relative(process.cwd(), outputPath)}`));
|
|
121
|
+
return;
|
|
122
|
+
} catch (error) {
|
|
123
|
+
console.log(chalk.red(`❌ guard 自动补录失败: ${error.message}`));
|
|
124
|
+
process.exitCode = 1;
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
console.log(chalk.red('\n❌ guard 未通过:检测到代码改动,但未检测到流程痕迹更新。'));
|
|
130
|
+
console.log(chalk.yellow('\n检测到的代码改动:'));
|
|
131
|
+
codeFiles.forEach((file) => console.log(chalk.yellow(` - ${file}`)));
|
|
132
|
+
|
|
133
|
+
console.log(chalk.cyan('\n补救建议(v0):'));
|
|
134
|
+
console.log(chalk.cyan(' 1) 运行: aodw-skill audit --write'));
|
|
135
|
+
console.log(chalk.cyan(` 2) 或手工更新 ${CORE_DIR}/06-project/ 或 RT/ 或 maintainers/ 下的记录`));
|
|
136
|
+
|
|
137
|
+
process.exitCode = 1;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export async function auditDocTrace(options = {}) {
|
|
141
|
+
const stagedFiles = getStagedFiles();
|
|
142
|
+
const splitResult = splitFiles(stagedFiles);
|
|
143
|
+
const outputPath = path.join(process.cwd(), CORE_DIR, '06-project', 'audit-latest.md');
|
|
144
|
+
|
|
145
|
+
if (options.write) {
|
|
146
|
+
writeAuditDraft(stagedFiles, splitResult);
|
|
147
|
+
console.log(chalk.green(`✅ 已写入审计草稿: ${path.relative(process.cwd(), outputPath)}`));
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
writeAuditDraft(stagedFiles, splitResult);
|
|
152
|
+
const content = await fs.readFile(outputPath, 'utf8');
|
|
153
|
+
await fs.remove(outputPath);
|
|
154
|
+
console.log(content);
|
|
155
|
+
}
|
|
156
|
+
|