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 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
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aodw-skill",
3
- "version": "0.7.24",
3
+ "version": "0.7.25",
4
4
  "description": "Next-channel CLI tool to scaffold AODW in your project",
5
5
  "main": "bin/aodw.js",
6
6
  "files": [