ai-spec-tool 0.1.2 → 0.1.4

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.
@@ -0,0 +1,223 @@
1
+ #问题询问
2
+
3
+ ACTION:
4
+ <<输出以下内容>>
5
+ ## [1/7] 请尽可能详细描述这个专案想要实现什么
6
+ ---
7
+ 1) 直接描述
8
+ <<输出以上内容,不得再输出任何其他内容>>
9
+ ACTION: 等待用户回答
10
+
11
+ 输出规则:
12
+ - __PLACEHOLDER__ 为占位符,请勿输出,而是基于规则输出动态内容
13
+ - __ADVICE__ 基于[先前的对话内容,所有依赖文件](如过有),结合问题给与建议答案
14
+ ACTION:
15
+ <<结合输出规则,输出以下内容>>
16
+ ## [2/7] 主要用户是哪一群人?
17
+ ---
18
+ 1) ✨__ADVICE__
19
+ 2) 直接描述
20
+ <<输出以上内容,不得再输出任何其他内容>>
21
+ ACTION: 等待用户回答
22
+
23
+ 输出规则:
24
+ - __PLACEHOLDER__ 为占位符,请勿输出,而是基于规则输出动态内容
25
+ - __ADVICE__ 基于[先前的对话内容,所有依赖文件](如过有),结合问题给与建议答案
26
+ ACTION:
27
+ <<结合输出规则,输出以下内容>>
28
+ ## [3/7] 专案希望解决谁的哪些问题?
29
+ - 可列出多条
30
+ ---
31
+ 1) ✨__ADVICE__
32
+ 2) 直接描述
33
+ <<输出以上内容,不得再输出任何其他内容>>
34
+ ACTION: 等待用户回答
35
+
36
+ ACTION:
37
+ <<输出以下内容>>
38
+ ## [4/7] 专案开发过程必须遵守哪些原则?
39
+ - 可列出多条
40
+ ---
41
+ 1) 无
42
+ 2) 指定技术框架为 <请描述>
43
+ 3) 快速开发
44
+ 4) 可长期维护
45
+ 5) 直接描述
46
+ <<输出以上内容,不得再输出任何其他内容>>
47
+ ACTION: 等待用户回答
48
+
49
+ 输出规则:
50
+ - __PLACEHOLDER__ 为占位符,请勿输出,而是基于规则输出动态内容
51
+ - __ADVICE__ 基于[先前的对话内容,所有依赖文件](如过有),结合问题给与建议答案
52
+ ACTION:
53
+ <<结合输出规则,输出以下内容>>
54
+ ## [5/7] 如何验收定义这个专案算完成了?
55
+ - 请列出1-3条关键目标
56
+ - 驗收目標必須可驗證
57
+ ---
58
+ 1) ✨__ADVICE__
59
+ 2) 直接描述
60
+ <<输出以上内容,不得再输出任何其他内容>>
61
+ ACTION: 等待用户回答
62
+
63
+ 输出规则:
64
+ - __PLACEHOLDER__ 为占位符,请勿输出,而是基于规则输出动态内容
65
+ - __ADVICE__ 基于[先前的对话内容,所有依赖文件](如过有),结合问题给与建议答案
66
+ ACTION:
67
+ <<结合输出规则,输出以下内容>>
68
+ ## [6/7] 专案是否服务于一个未来更大的目标?
69
+ - 可列出多条
70
+ ---
71
+ 1) ✨__ADVICE__
72
+ 2) 无
73
+ 3) 直接描述
74
+ <<输出以上内容,不得再输出任何其他内容>>
75
+ ACTION: 等待用户回答
76
+
77
+ 输出规则:
78
+ - __PLACEHOLDER__ 为占位符,请勿输出,而是基于规则输出动态内容
79
+ - __ADVICE__ 基于[先前的对话内容,所有依赖文件](如过有),需求风险:问题是否被正确理解,技术风险:实现难度、性能、依赖不确定性,数据风险:数据来源、质量、权限,用户风险:获取、留存、使用成本,合规与安全风险:若相关,用以上几个维度去思考,结合问题给与建议答案
80
+ ACTION:
81
+ <<结合输出规则,输出以下内容>>
82
+ ## [7/7] 专案未来可能的风险?
83
+ - 可列出多条
84
+ ---
85
+ 1) ✨__ADVICE__
86
+ 2) 无
87
+ 3) 直接描述
88
+ <<输出以上内容,不得再输出任何其他内容>>
89
+ ACTION: 等待用户回答
90
+
91
+ #输出文件
92
+ *仅在以上所有流程完成后才执行
93
+
94
+ 输出格式:Markdown
95
+ 语言:中文为主
96
+ 输出文件内容规则:
97
+ __PLACEHOLDER__ 为占位符,请勿输出,而是基于规则输出动态内容
98
+ __2__ 基于所有用户回答判断项目性质。
99
+ 若项目具有长期延续性或未来扩展方向,则输出:
100
+ 长期意义一句话:____。
101
+ 项目在强化的能力:列 1~3 条(动词 + 名词)。能力必须可在未来复用或延续。
102
+ 若项目属于短期或阶段性交付,则输出:
103
+ 阶段意义一句话:____。
104
+ 该阶段完成后沉淀的资产或能力:列 1~3 条(例如流程经验、系统能力、数据基础)。
105
+ 不得假设平台化或规模化,除非用户明确提到。
106
+
107
+ __3__ 现状成本或痛点:从用户问题描述中归纳 1~3 条。
108
+ 预期收益:必须与验收目标逐条映射,并明确写出该收益解决的是哪条痛点。
109
+ 每条收益需同时标注 对应痛点 与 对应验收目标编号。
110
+ 若验收目标不可验证或过于聚合,指出其是否缺少拆分维度(功能层/行为层/价值层)。
111
+
112
+ __4__ 输出 2~5 条可被验证的假设。
113
+ 假设必须分为三类中的任意组合:功能成立假设、用户行为假设、价值成立假设。
114
+ 格式为:假设:如果____,那么____。
115
+ 验证方式:对应哪条验收目标。
116
+ 所有假设必须可映射到已有验收目标。
117
+ 不得生成无法验证的抽象目标。
118
+
119
+ __5__ 优先用户定义:使用主要用户定义的原话或贴近改写,不得新增人群。
120
+ 核心问题:从用户问题描述中归纳 1~3 条。
121
+ 同时指出:这些问题是否具有迫切性或高频性,依据必须来自原回答。
122
+ 若用户群过宽,说明其是否会导致策略分散。
123
+
124
+ __6__ 风险清单:直接引用或归纳风险列表中的 1~5 条。
125
+ 每条风险标注为 需求、技术、数据、用户 或 合规。
126
+ 同时指出:该风险若发生,将直接影响哪条验收目标。
127
+ 不得新增风险类型或缓解方案。
128
+
129
+ __8__ 不得假设存在支付、订阅、权限或渠道等机制,除非用户明确提到。
130
+ MVP闭环一句话:用户通过____完成____从而达到____,必须可映射到验收目标。
131
+ 闭环步骤 3~7 步,使用中性动词。
132
+ 关键断点:指出哪一步失败将直接导致哪条验收目标无法达成。
133
+ 必须能力 3~6 条,每条注明对应的验收目标编号,并说明其属于 功能能力 或 系统能力。
134
+ 可延后能力:列出不会影响任何验收目标的能力。
135
+
136
+ __9__ 将每条验收目标拆解为 指标 + 条件 + 判定方式。
137
+ 若多个能力集中映射到同一条验收目标,指出该验收目标是否过度聚合。
138
+ 不得新增额外商业指标。
139
+
140
+ __10__ 列出 3~7 条不可违反原则,优先使用用户原话。
141
+ 将“快速开发”“长期维护”“指定技术框架”等表述转写为可执行约束句式。
142
+ 同时指出:这些原则将限制哪些技术或设计选择。
143
+
144
+ __12__ 基于验收目标范围与开发原则推导边界。
145
+ 输出 3~7 条,每条格式为 不做____,因为其不会影响当前验收目标或会增加复杂度。
146
+ 若验收目标范围过大,指出其是否需要拆分阶段。
147
+
148
+ __13__ 仅提取包含 必须、禁止、绝不 或 只能 等强制语气的内容。
149
+ 若无明确硬约束,输出 无。
150
+
151
+ 输出文件内容结构:
152
+ <<以下为实际输出文件格式,需结合输出规则生成部分动态内容>>
153
+ #🤔为什么做
154
+ ##这个项目存在的长期意义是什么?
155
+ __2__
156
+
157
+ ##为什么现在值得做?
158
+ __3__
159
+
160
+ ##关键假设是什么?
161
+ __4__
162
+
163
+ ##第一优先用户是谁?
164
+ __5__
165
+
166
+ ##最大战略风险是什么?
167
+ __6__
168
+
169
+ #✅要做什么
170
+ ##MVP 的最小闭环是什么?
171
+ __8__
172
+
173
+ ##成功如何被量化?
174
+ __9__
175
+
176
+ ##核心系统架构原则
177
+ __10__
178
+
179
+ #⛔守则
180
+ ##本阶段明确不做什么
181
+ __12__
182
+
183
+ ##不可突破的硬性约束是什么?
184
+ __13__
185
+
186
+ <<以上为实际输出文件格式,需结合输出规则生成部分动态内容>>
187
+
188
+ [MARK:1]
189
+ 输出规则:
190
+ - __PLACEHOLDER__ 为占位符,请勿输出,而是基于规则输出动态内容
191
+ - __输出文件内容__ 显示基于[输出文件内容规则/输出文件内容结构]总结输出文件内容
192
+ ACTION:
193
+ <<结合输出规则,输出以下内容>>
194
+ ```md
195
+ __输出文件内容__
196
+ ```
197
+ <<输出以上内容>>
198
+ ACTION:
199
+ <<输出以下内容>>
200
+ ## 是否接受将以上总结输出并保存成文件?
201
+ ---
202
+ 1) 接受
203
+ 2) 提出修改
204
+ <<输出以上内容,不得再输出任何其他内容>>
205
+ IF: 用户选择接受
206
+ ACTION: 将最终确认的输出版本保存到./docs/global/vision.md
207
+ ACTION:
208
+ <<输出以下内容>>
209
+ > ✅文件已保存于./docs/global/vision.md
210
+ <<输出以上内容>>
211
+ ENDIF
212
+
213
+ IF: 用户没有选择接受
214
+ ACTION: 带着用户的修改提议,回到[MARK:1]重新执行
215
+ ENDIF
216
+
217
+ ACTION:
218
+ <<输出以下内容>>
219
+ ## 已完成Vision文件的生成,接下来...
220
+ ---
221
+ 1) 生成architecture文件
222
+ 2) 直接描述
223
+ <<输出以上内容,不得再输出任何其他内容>>
@@ -2,10 +2,12 @@
2
2
 
3
3
  const fs = require("fs");
4
4
  const path = require("path");
5
+ const readline = require("readline");
5
6
 
6
7
  const ASSET_ROOT = path.resolve(__dirname, "..", "assets");
7
8
  const TEMPLATE_AGENTS = path.join(ASSET_ROOT, "AGENTS.md");
8
9
  const TEMPLATE_AGENTS_DIR = path.join(ASSET_ROOT, ".agents");
10
+ const TEMPLATE_DOCS_DIR = path.join(ASSET_ROOT, "docs");
9
11
 
10
12
  const START = "<!-- AI-SPEC-TOOL:START -->";
11
13
  const END = "<!-- AI-SPEC-TOOL:END -->";
@@ -32,7 +34,7 @@ function nextIncomingPath(destPath) {
32
34
  return `${candidate}.${i}`;
33
35
  }
34
36
 
35
- function copyFileSafe(srcPath, destPath, report) {
37
+ function copyFileSafe(srcPath, destPath, report, options) {
36
38
  if (!fs.existsSync(destPath)) {
37
39
  ensureDir(path.dirname(destPath));
38
40
  fs.copyFileSync(srcPath, destPath);
@@ -43,12 +45,17 @@ function copyFileSafe(srcPath, destPath, report) {
43
45
  report.skipped.push(destPath);
44
46
  return;
45
47
  }
48
+ if (options.force) {
49
+ fs.copyFileSync(srcPath, destPath);
50
+ report.updated.push(destPath);
51
+ return;
52
+ }
46
53
  const incoming = nextIncomingPath(destPath);
47
54
  fs.copyFileSync(srcPath, incoming);
48
55
  report.conflicts.push({ target: destPath, incoming });
49
56
  }
50
57
 
51
- function copyDirSafe(srcDir, destDir, report) {
58
+ function copyDirSafe(srcDir, destDir, report, options) {
52
59
  ensureDir(destDir);
53
60
  const entries = fs.readdirSync(srcDir, { withFileTypes: true });
54
61
  entries.forEach((entry) => {
@@ -56,15 +63,42 @@ function copyDirSafe(srcDir, destDir, report) {
56
63
  const src = path.join(srcDir, entry.name);
57
64
  const dest = path.join(destDir, entry.name);
58
65
  if (entry.isDirectory()) {
59
- copyDirSafe(src, dest, report);
66
+ copyDirSafe(src, dest, report, options);
60
67
  return;
61
68
  }
62
69
  if (entry.isFile()) {
63
- copyFileSafe(src, dest, report);
70
+ copyFileSafe(src, dest, report, options);
64
71
  }
65
72
  });
66
73
  }
67
74
 
75
+ function askYesNo(prompt) {
76
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
77
+ return new Promise((resolve) => {
78
+ rl.question(prompt, (answer) => {
79
+ rl.close();
80
+ const normalized = String(answer || "").trim().toLowerCase();
81
+ resolve(normalized === "y" || normalized === "yes");
82
+ });
83
+ });
84
+ }
85
+
86
+ async function resolveConflictsInteractively(report, options) {
87
+ if (!report.conflicts.length || !options.ask) return;
88
+ if (!process.stdin.isTTY) return;
89
+
90
+ for (const conflict of report.conflicts) {
91
+ const prompt = `Overwrite ${conflict.target} with incoming? (y/N) `;
92
+ const yes = await askYesNo(prompt);
93
+ if (yes) {
94
+ fs.copyFileSync(conflict.incoming, conflict.target);
95
+ report.updated.push(conflict.target);
96
+ report.conflictsResolved.push(conflict.target);
97
+ fs.unlinkSync(conflict.incoming);
98
+ }
99
+ }
100
+ }
101
+
68
102
  function updateAgentsMd(targetPath, report) {
69
103
  const template = readFile(TEMPLATE_AGENTS).trimEnd();
70
104
  const block = `${START}\n${template}\n${END}\n`;
@@ -99,7 +133,7 @@ function updateAgentsMd(targetPath, report) {
99
133
  report.updated.push(targetPath);
100
134
  }
101
135
 
102
- function init() {
136
+ async function init(options) {
103
137
  const cwd = process.cwd();
104
138
  const targetAgents = path.join(cwd, "AGENTS.md");
105
139
  const targetAgentsDir = path.join(cwd, ".agents");
@@ -108,7 +142,8 @@ function init() {
108
142
  added: [],
109
143
  updated: [],
110
144
  skipped: [],
111
- conflicts: []
145
+ conflicts: [],
146
+ conflictsResolved: []
112
147
  };
113
148
 
114
149
  if (!fs.existsSync(TEMPLATE_AGENTS) || !fs.existsSync(TEMPLATE_AGENTS_DIR)) {
@@ -116,14 +151,22 @@ function init() {
116
151
  process.exit(1);
117
152
  }
118
153
 
119
- copyDirSafe(TEMPLATE_AGENTS_DIR, targetAgentsDir, report);
154
+ copyDirSafe(TEMPLATE_AGENTS_DIR, targetAgentsDir, report, options);
155
+ if (fs.existsSync(TEMPLATE_DOCS_DIR)) {
156
+ const targetDocsDir = path.join(cwd, "docs");
157
+ copyDirSafe(TEMPLATE_DOCS_DIR, targetDocsDir, report, options);
158
+ }
120
159
  updateAgentsMd(targetAgents, report);
160
+ await resolveConflictsInteractively(report, options);
121
161
 
122
162
  console.log("ai-spec-tool init complete.");
123
163
  console.log(`Added: ${report.added.length}`);
124
164
  console.log(`Updated: ${report.updated.length}`);
125
165
  console.log(`Skipped: ${report.skipped.length}`);
126
166
  console.log(`Conflicts: ${report.conflicts.length}`);
167
+ if (report.conflictsResolved.length) {
168
+ console.log(`Resolved: ${report.conflictsResolved.length}`);
169
+ }
127
170
 
128
171
  if (report.conflicts.length) {
129
172
  console.log("\nConflicts (new versions saved as .incoming):");
@@ -134,13 +177,20 @@ function init() {
134
177
  }
135
178
 
136
179
  function main() {
137
- const [command] = process.argv.slice(2);
180
+ const [command, ...args] = process.argv.slice(2);
181
+ const options = {
182
+ force: args.includes("--force"),
183
+ ask: args.includes("--ask")
184
+ };
138
185
  if (!command || command === "help" || command === "--help" || command === "-h") {
139
- console.log("ai-spec-tool\n\nUsage:\n ai-spec-tool init\n");
186
+ console.log("ai-spec-tool\n\nUsage:\n ai-spec-tool init [--force|--ask]\n");
140
187
  return;
141
188
  }
142
189
  if (command === "init") {
143
- init();
190
+ init(options).catch((err) => {
191
+ console.error(err?.message || err);
192
+ process.exit(1);
193
+ });
144
194
  return;
145
195
  }
146
196
  console.error(`Unknown command: ${command}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-spec-tool",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Installable agents + rules for AI spec workflows",
5
5
  "bin": {
6
6
  "ai-spec-tool": "bin/ai-spec-tool.js"
@@ -1,32 +0,0 @@
1
- ---
2
- name: bugfix
3
- description: 用户提供报错信息或描述异常行为时使用
4
- ---
5
-
6
- ## 依赖(必须存在且必须遵守)
7
- - docs/global/naming-rule.md
8
- - docs/global/vision.md
9
- - docs/global/architecture.md
10
-
11
-
12
- ## 流程(严格执行)
13
-
14
- ### 1) 依赖检查(强制)
15
- - 你必须先尝试读取并理解所有依赖文件内容。
16
- - 若任何依赖文件无法读取/不存在/内容为空:视为“依赖未满足”,进入步骤 2。
17
- - 若所有依赖已满足:进入步骤 3。
18
-
19
- ### 2) 依赖缺失
20
- - 你必须只输出以下内容(禁止其他内容):
21
- `缺少依赖[<依赖名称>]\n回复「Y」开始创建依赖。`
22
- - 下一run使用 skill: rules-creator
23
-
24
- ### 3) 依赖满足
25
- - 帮助用户修正问题
26
-
27
-
28
-
29
-
30
-
31
-
32
-