aodw-skill 0.7.23 → 0.7.25-beta.1

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,32 @@ 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
+
126
+ 在当前项目安装 `pre-commit` guard,自动处理:
127
+ - 改了代码目录(如 `cli/`、`templates/`、`src/` 等)
128
+ - 但没有任何流程痕迹更新(如 `RT/`、`maintainers/`、`.aodw-next/06-project/`)
129
+ - 自动生成并暂存 `.aodw-next/06-project/audit-latest.md` 作为最小补录
130
+
131
+ 手动命令:
132
+
133
+ ```bash
134
+ npx aodw-skill guard
135
+ npx aodw-skill audit --write
136
+ ```
137
+
138
+ 用途:
139
+ - `guard`: 提交前检查是否“有改动无痕迹”
140
+ - `audit --write`: 生成最小补录草稿到 `.aodw-next/06-project/audit-latest.md`
141
+
116
142
  ## 维护者发布流程
117
143
 
118
144
  后续版本发布统一采用以下方式:
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) {
@@ -10,7 +10,7 @@ const OVERVIEW_FILE = `${CORE_DIR}/06-project/ai-overview.md`;
10
10
  const MODULES_INDEX_FILE = `${CORE_DIR}/06-project/modules-index.yaml`;
11
11
 
12
12
  // 检测技术栈
13
- async function detectTechStack() {
13
+ export async function detectTechStack() {
14
14
  const cwd = process.cwd();
15
15
  const techStack = {
16
16
  frontend: [],
@@ -228,7 +228,7 @@ async function detectTechStack() {
228
228
  }
229
229
 
230
230
  // 分析目录结构
231
- async function analyzeDirectoryStructure() {
231
+ export async function analyzeDirectoryStructure() {
232
232
  const cwd = process.cwd();
233
233
  const structure = [];
234
234
  const keyDirs = ['frontend', 'backend', 'apps', 'packages', 'src', 'lib', 'infra', 'docs', 'RT', CORE_DIR];
@@ -275,7 +275,7 @@ function getDefaultDescription(dir) {
275
275
  }
276
276
 
277
277
  // 识别模块
278
- async function detectModules() {
278
+ export async function detectModules() {
279
279
  const cwd = process.cwd();
280
280
  const modules = [];
281
281
  const seenModules = new Set(); // 避免重复
@@ -609,7 +609,7 @@ AI 在创建新模块或重构模块结构时,应同步维护此映射关系
609
609
  }
610
610
 
611
611
  // 生成 ai-overview.md
612
- async function generateOverviewFile(merged, existing, modules) {
612
+ export async function generateOverviewFile(merged, existing, modules) {
613
613
  // 生成架构概览部分
614
614
  const architectureSection = generateArchitectureSection(existing?.sections?.architecture);
615
615
 
@@ -681,7 +681,7 @@ ${moduleMappingSection}
681
681
  }
682
682
 
683
683
  // 生成 modules-index.yaml
684
- async function generateModulesIndex(modules) {
684
+ export async function generateModulesIndex(modules) {
685
685
  const index = {
686
686
  version: 1,
687
687
  last_updated: new Date().toISOString(),
@@ -0,0 +1,65 @@
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
+ npx --yes aodw-skill guard --auto-fix --stage-audit
20
+ exit $?
21
+ `;
22
+ }
23
+
24
+ export async function enableGuardHook(options = {}) {
25
+ const { silent = false } = options;
26
+ const hookPath = getHookPath();
27
+ const gitDir = path.join(process.cwd(), '.git');
28
+
29
+ if (!fs.existsSync(gitDir)) {
30
+ if (!silent) {
31
+ console.log(chalk.red('❌ 当前目录不是 git 仓库,无法安装 pre-commit hook。'));
32
+ }
33
+ process.exitCode = 1;
34
+ return;
35
+ }
36
+
37
+ await fs.ensureDir(path.dirname(hookPath));
38
+
39
+ let shouldOverwrite = true;
40
+ if (fs.existsSync(hookPath)) {
41
+ const existing = await fs.readFile(hookPath, 'utf8');
42
+ if (!existing.includes('AODW v0 trace guard hook')) {
43
+ if (!silent) {
44
+ console.log(chalk.yellow('⚠️ 检测到已有 pre-commit hook。'));
45
+ console.log(chalk.yellow(' 为避免覆盖你的自定义逻辑,本次不自动改写。'));
46
+ console.log(chalk.cyan(' 建议手工在现有 hook 中加入: aodw-skill guard --auto-fix --stage-audit'));
47
+ }
48
+ shouldOverwrite = false;
49
+ }
50
+ }
51
+
52
+ if (!shouldOverwrite) {
53
+ process.exitCode = 1;
54
+ return;
55
+ }
56
+
57
+ await fs.writeFile(hookPath, getHookContent(), 'utf8');
58
+ await fs.chmod(hookPath, 0o755);
59
+
60
+ if (!silent) {
61
+ console.log(chalk.green('✅ 已启用 AODW v0 guard pre-commit hook。'));
62
+ console.log(chalk.gray(' 提交时将自动检查并自动补录最小流程痕迹。'));
63
+ }
64
+ }
65
+
@@ -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.23",
3
+ "version": "0.7.25-beta.1",
4
4
  "description": "Next-channel CLI tool to scaffold AODW in your project",
5
5
  "main": "bin/aodw.js",
6
6
  "files": [