@chenguangyao/devflow-kit 0.1.43

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.
Files changed (198) hide show
  1. package/CHANGELOG.md +232 -0
  2. package/LICENSE +21 -0
  3. package/README.md +539 -0
  4. package/bin/devflow.js +9 -0
  5. package/docs/RFC-001-devflow-kit.md +617 -0
  6. package/docs/RFC-002-workflow-kernel.md +134 -0
  7. package/docs/enterprise-integration-supplement.md +274 -0
  8. package/docs/internal-gitlab-setup.md +426 -0
  9. package/docs/marketplace-skills.md +231 -0
  10. package/docs/migration-from-arb.md +232 -0
  11. package/docs/tooling-overview.md +774 -0
  12. package/docs/workflow-orchestration.md +695 -0
  13. package/docs/workflow-ui-prototype.html +271 -0
  14. package/package.json +52 -0
  15. package/schemas/config.schema.json +51 -0
  16. package/schemas/delta.schema.json +22 -0
  17. package/schemas/state.schema.json +130 -0
  18. package/schemas/status-surface.schema.json +197 -0
  19. package/schemas/workflow-confirmation-surface.schema.json +70 -0
  20. package/schemas/workflow-picker.schema.json +94 -0
  21. package/scripts/postinstall.js +101 -0
  22. package/scripts/render-workflow-ui-prototype.js +271 -0
  23. package/skills/apply/SKILL.md +313 -0
  24. package/skills/apply/references/discipline-checklist.md +145 -0
  25. package/skills/apply/references/subagent-implementer-prompt.md +113 -0
  26. package/skills/apply/references/subagent-orchestration.md +150 -0
  27. package/skills/apply/references/subagent-reviewer-prompt.md +180 -0
  28. package/skills/apply/references/tdd-loop.md +287 -0
  29. package/skills/apply/references/when-plan-is-wrong.md +279 -0
  30. package/skills/apply/references/worktree-swarm.md +292 -0
  31. package/skills/archive/SKILL.md +229 -0
  32. package/skills/archive/references/conflict-resolution.md +336 -0
  33. package/skills/archive/references/knowledge-deposit.md +381 -0
  34. package/skills/archive/references/spec-merge.md +365 -0
  35. package/skills/brainstorm/SKILL.md +123 -0
  36. package/skills/brainstorm/references/proposal-template.md +244 -0
  37. package/skills/brainstorm/references/question-catalog.md +168 -0
  38. package/skills/brainstorm/references/session-template.md +184 -0
  39. package/skills/ci-fix/SKILL.md +63 -0
  40. package/skills/ci-fix/references/loop.md +25 -0
  41. package/skills/code-review/SKILL.md +279 -0
  42. package/skills/code-review/references/escalation-playbook.md +192 -0
  43. package/skills/code-review/references/language-cheatsheets/go.md +175 -0
  44. package/skills/code-review/references/language-cheatsheets/java-spring-mybatis.md +246 -0
  45. package/skills/code-review/references/language-cheatsheets/python.md +170 -0
  46. package/skills/code-review/references/language-cheatsheets/vue.md +199 -0
  47. package/skills/code-review/references/output-template.md +275 -0
  48. package/skills/code-review/references/review-checklist.md +251 -0
  49. package/skills/complexity-grading/SKILL.md +259 -0
  50. package/skills/deliver/SKILL.md +271 -0
  51. package/skills/deliver/references/delivery-modes.md +299 -0
  52. package/skills/deliver/references/notify.md +359 -0
  53. package/skills/deliver/references/pr-description.md +319 -0
  54. package/skills/dependency-upgrade/SKILL.md +57 -0
  55. package/skills/dependency-upgrade/references/risk-matrix.md +38 -0
  56. package/skills/df-orchestrator/SKILL.md +407 -0
  57. package/skills/df-orchestrator/references/complexity-grading.md +177 -0
  58. package/skills/df-orchestrator/references/escalation-matrix.md +191 -0
  59. package/skills/df-orchestrator/references/routing-rules.md +290 -0
  60. package/skills/df-orchestrator/references/workflow-state-machine.md +208 -0
  61. package/skills/frontend-quality/SKILL.md +61 -0
  62. package/skills/frontend-quality/references/checklist.md +35 -0
  63. package/skills/handoff-resume/SKILL.md +59 -0
  64. package/skills/handoff-resume/references/handoff-template.md +54 -0
  65. package/skills/plan/SKILL.md +166 -0
  66. package/skills/plan/references/task-breakdown.md +207 -0
  67. package/skills/plan/references/task-sequencing.md +143 -0
  68. package/skills/plan/references/task-template.md +248 -0
  69. package/skills/requirement-analysis/SKILL.md +499 -0
  70. package/skills/requirement-analysis/references/acceptance-criteria.md +183 -0
  71. package/skills/requirement-analysis/references/code-recon.md +151 -0
  72. package/skills/requirement-analysis/references/edge-case-catalog.md +164 -0
  73. package/skills/requirement-analysis/references/requirement-template.md +339 -0
  74. package/skills/requirement-analysis/references/scope-negotiation.md +162 -0
  75. package/skills/security-hardening/SKILL.md +60 -0
  76. package/skills/security-hardening/references/checklist.md +42 -0
  77. package/skills/tech-spec/SKILL.md +388 -0
  78. package/skills/tech-spec/references/api-contract-design.md +172 -0
  79. package/skills/tech-spec/references/decision-records.md +110 -0
  80. package/skills/tech-spec/references/design-template.md +301 -0
  81. package/skills/tech-spec/references/rollout-and-rollback.md +203 -0
  82. package/skills/tech-spec/references/spec-delta-conventions.md +250 -0
  83. package/skills/tech-spec/references/transaction-patterns.md +212 -0
  84. package/skills/test-spec/SKILL.md +219 -0
  85. package/skills/test-spec/references/coverage-strategy.md +218 -0
  86. package/skills/test-spec/references/edge-case-to-test.md +143 -0
  87. package/skills/test-spec/references/test-case-template.md +276 -0
  88. package/skills/verify/SKILL.md +232 -0
  89. package/skills/verify/references/nfr-verification.md +292 -0
  90. package/skills/verify/references/report-templates.md +510 -0
  91. package/skills/verify/references/self-test-guide.md +240 -0
  92. package/skills/verify/references/verify-rollback-map.md +247 -0
  93. package/src/cli/commands/_helpers.js +108 -0
  94. package/src/cli/commands/_submit.js +718 -0
  95. package/src/cli/commands/apply.js +198 -0
  96. package/src/cli/commands/archive.js +180 -0
  97. package/src/cli/commands/checkpoint.js +113 -0
  98. package/src/cli/commands/deliver.js +377 -0
  99. package/src/cli/commands/deploy.js +504 -0
  100. package/src/cli/commands/design.js +158 -0
  101. package/src/cli/commands/disable.js +21 -0
  102. package/src/cli/commands/doctor.js +178 -0
  103. package/src/cli/commands/enable.js +21 -0
  104. package/src/cli/commands/flow.js +645 -0
  105. package/src/cli/commands/help.js +93 -0
  106. package/src/cli/commands/ingest.js +602 -0
  107. package/src/cli/commands/init.js +341 -0
  108. package/src/cli/commands/knowledge.js +523 -0
  109. package/src/cli/commands/logs.js +43 -0
  110. package/src/cli/commands/new.js +202 -0
  111. package/src/cli/commands/plan.js +49 -0
  112. package/src/cli/commands/propose.js +27 -0
  113. package/src/cli/commands/provider.js +698 -0
  114. package/src/cli/commands/report.js +143 -0
  115. package/src/cli/commands/requirement.js +227 -0
  116. package/src/cli/commands/review.js +301 -0
  117. package/src/cli/commands/skills.js +457 -0
  118. package/src/cli/commands/status.js +925 -0
  119. package/src/cli/commands/switch.js +27 -0
  120. package/src/cli/commands/sync.js +47 -0
  121. package/src/cli/commands/test.js +366 -0
  122. package/src/cli/commands/uninstall.js +32 -0
  123. package/src/cli/commands/update.js +74 -0
  124. package/src/cli/commands/verify.js +354 -0
  125. package/src/cli/commands/worktree.js +78 -0
  126. package/src/cli/index.js +72 -0
  127. package/src/cli/parse-args.js +102 -0
  128. package/src/core/autodetect.js +271 -0
  129. package/src/core/change.js +208 -0
  130. package/src/core/checkpoint.js +217 -0
  131. package/src/core/config.js +60 -0
  132. package/src/core/delta.js +290 -0
  133. package/src/core/markers.js +59 -0
  134. package/src/core/paths.js +173 -0
  135. package/src/core/plan-tasks.js +36 -0
  136. package/src/core/project-routing.js +285 -0
  137. package/src/core/projects.js +200 -0
  138. package/src/core/state.js +200 -0
  139. package/src/core/workflow-check.js +177 -0
  140. package/src/core/workflow-init.js +34 -0
  141. package/src/core/workflow-picker.js +154 -0
  142. package/src/core/workflow-policy.js +119 -0
  143. package/src/core/workflow-suggest.js +181 -0
  144. package/src/core/workflow-verify.js +88 -0
  145. package/src/core/workflow.js +433 -0
  146. package/src/core/worktree.js +241 -0
  147. package/src/knowledge/categories.js +107 -0
  148. package/src/knowledge/classify.js +125 -0
  149. package/src/knowledge/deposit.js +414 -0
  150. package/src/knowledge/migrate.js +149 -0
  151. package/src/knowledge/mr.js +219 -0
  152. package/src/knowledge/query.js +131 -0
  153. package/src/knowledge/registry.js +151 -0
  154. package/src/knowledge/sync.js +179 -0
  155. package/src/providers/base.js +74 -0
  156. package/src/providers/drivers/api-yapi.js +78 -0
  157. package/src/providers/drivers/ci-jenkins.js +109 -0
  158. package/src/providers/drivers/intake-confluence.js +544 -0
  159. package/src/providers/drivers/kb-git.js +549 -0
  160. package/src/providers/drivers/kb-weknora.js +472 -0
  161. package/src/providers/drivers/notify-smtp.js +515 -0
  162. package/src/providers/drivers/observability-oss.js +43 -0
  163. package/src/providers/drivers/observability-sls.js +50 -0
  164. package/src/providers/lifecycle.js +135 -0
  165. package/src/providers/loader.js +132 -0
  166. package/src/providers/local.js +190 -0
  167. package/src/providers/userconfig.js +283 -0
  168. package/src/reports/aggregate.js +185 -0
  169. package/src/reports/coverage.js +163 -0
  170. package/src/reports/detect.js +143 -0
  171. package/src/reports/parse.js +236 -0
  172. package/src/templates/files/ci/github.yml +38 -0
  173. package/src/templates/files/ci/gitlab.yml +27 -0
  174. package/src/templates/files/design.md +63 -0
  175. package/src/templates/files/ide/devflow-workflow.md +58 -0
  176. package/src/templates/files/ide/project-overview-reference.md +1 -0
  177. package/src/templates/files/ide/project-overview.md +27 -0
  178. package/src/templates/files/knowledge-index.json +17 -0
  179. package/src/templates/files/knowledge.md +28 -0
  180. package/src/templates/files/meta.json +8 -0
  181. package/src/templates/files/plan.md +38 -0
  182. package/src/templates/files/proposal.md +33 -0
  183. package/src/templates/files/reports/contract-test.md +40 -0
  184. package/src/templates/files/reports/e2e-test.md +30 -0
  185. package/src/templates/files/reports/integration-test.md +36 -0
  186. package/src/templates/files/reports/joint-test.md +58 -0
  187. package/src/templates/files/reports/perf.md +24 -0
  188. package/src/templates/files/reports/regression.md +20 -0
  189. package/src/templates/files/reports/remote-test.md +55 -0
  190. package/src/templates/files/reports/self-test.md +43 -0
  191. package/src/templates/files/reports/smoke-test.md +22 -0
  192. package/src/templates/files/reports/unit-test.md +36 -0
  193. package/src/templates/files/requirement.md +51 -0
  194. package/src/templates/files/review.md +38 -0
  195. package/src/templates/files/tests.md +36 -0
  196. package/src/templates/files/verify.md +32 -0
  197. package/src/templates/index.js +21 -0
  198. package/src/utils/log.js +37 -0
@@ -0,0 +1,341 @@
1
+ 'use strict';
2
+ const fs = require('fs/promises');
3
+ const fsSync = require('fs');
4
+ const path = require('path');
5
+ const log = require('../../utils/log.js');
6
+ const paths = require('../../core/paths.js');
7
+ const config = require('../../core/config.js');
8
+ const markers = require('../../core/markers.js');
9
+ const templates = require('../../templates/index.js');
10
+ const autodetect = require('../../core/autodetect.js');
11
+ const skillsCmd = require('./skills.js');
12
+ const userconfig = require('../../providers/userconfig.js');
13
+
14
+ const IDE_TARGETS = {
15
+ claude: '.claude/CLAUDE.md',
16
+ cursor: '.cursor/rules/devflow.mdc',
17
+ agents: 'AGENTS.md',
18
+ };
19
+
20
+ const PROJECT_OVERVIEW_CANDIDATES = [
21
+ '.claude/CLAUDE.md',
22
+ 'CLAUDE.md',
23
+ 'PROJECT.md',
24
+ 'AGENTS.md',
25
+ '.cursor/rules/project.mdc',
26
+ 'docs/project-overview.md',
27
+ 'docs/architecture.md',
28
+ 'README.md',
29
+ ];
30
+
31
+ async function run({ flags = {}, cwd }) {
32
+ const root = cwd || process.cwd();
33
+ log.info(`initializing devflow in ${root}`);
34
+
35
+ const targetsArg = flags.targets || flags.target;
36
+ const targets = (typeof targetsArg === 'string')
37
+ ? targetsArg.split(',').map((s) => s.trim()).filter(Boolean)
38
+ : ['claude', 'cursor', 'agents'];
39
+
40
+ const overviewMode = flags['overview-mode'] || flags.overviewMode || 'reference';
41
+
42
+ // IDE mode controls whether we write the per-project IDE marker files
43
+ // (.claude/CLAUDE.md, .cursor/rules/devflow.mdc, AGENTS.md):
44
+ //
45
+ // full — write workflow + project-overview to every selected target
46
+ // (legacy behavior; useful when no user-wide skills are installed)
47
+ // minimal — skip all IDE writes; rely on user-wide skill +
48
+ // devflow/config.json#projectOverview.referencedFile to brief the AI
49
+ // none — same as minimal but also doesn't print any "skipped" log noise
50
+ //
51
+ // Default = 'auto':
52
+ // user-wide skills detected ? minimal : full
53
+ //
54
+ // Reasoning: when ~/.cursor/skills/devflow-kit/ is present, the orchestrator
55
+ // SKILL.md already documents every command and gate. Per-project IDE files
56
+ // become pure duplication of static workflow content — only the project-
57
+ // overview reference is per-project, but the skill reads that directly from
58
+ // devflow/config.json on session start, so even THAT can be skipped.
59
+ const ideModeRaw = flags.ide || flags['ide-mode'] || flags.ideMode || 'auto';
60
+ if (!['auto', 'full', 'minimal', 'none'].includes(ideModeRaw)) {
61
+ log.warn(`unknown --ide=${ideModeRaw} (expected: auto|full|minimal|none); falling back to auto`);
62
+ }
63
+ const ideMode = ['full', 'minimal', 'none'].includes(ideModeRaw)
64
+ ? ideModeRaw
65
+ : (skillsCmd.userSkillsInstalled() ? 'minimal' : 'full');
66
+
67
+ await ensureSkeleton(root);
68
+
69
+ // Ensure ~/.devflow/providers.json exists (empty skeleton, chmod 0600).
70
+ // All provider credentials live there by default; project-level
71
+ // <repo>/devflow/providers.json may override individual entries.
72
+ try {
73
+ const uc = await userconfig.ensureUserConfig();
74
+ if (uc.created.providers) {
75
+ log.ok(`user config: ${path.join('~', '.devflow', 'providers.json')} (created, empty)`);
76
+ if (uc.created.example) log.dim(`provider example: ${path.join('~', '.devflow', 'providers.example.json')}`);
77
+ log.dim(' fill it via "devflow provider add <name>" or edit directly');
78
+ } else {
79
+ log.dim(`user config: ${path.join('~', '.devflow', 'providers.json')} (already exists)`);
80
+ }
81
+ } catch (err) {
82
+ log.warn(`user config bootstrap failed: ${err.message}`);
83
+ }
84
+
85
+ const cfg = await config.ensure(root);
86
+ cfg.ide = cfg.ide || {};
87
+ cfg.ide.targets = targets;
88
+ cfg.ide.mode = ideMode;
89
+ cfg.projectOverview = cfg.projectOverview || {};
90
+ cfg.projectOverview.mode = overviewMode;
91
+
92
+ // Auto-detect project attributes (git remote / language / build tool / jenkins candidates)
93
+ const skipDetect = flags['no-detect'] === true || flags.detect === false;
94
+ if (!skipDetect) {
95
+ const d = await autodetect.detect(root);
96
+ cfg.detect = {
97
+ remoteUrl: d.remoteUrl || null,
98
+ remote: d.remote || null,
99
+ language: d.language,
100
+ buildTool: d.buildTool || null,
101
+ defaultBranch: d.defaultBranch || 'main',
102
+ jenkinsJobCandidates: d.jenkinsJobs,
103
+ detectedAt: new Date().toISOString(),
104
+ };
105
+ log.ok('autodetect:');
106
+ for (const line of autodetect.formatSummary(d).split('\n')) log.raw(line);
107
+ }
108
+
109
+ // Detect project overview source
110
+ const overviewSource = await detectProjectOverview(root, cfg.projectOverview.candidates || PROJECT_OVERVIEW_CANDIDATES);
111
+ cfg.projectOverview.referencedFile = overviewSource;
112
+ await config.write(root, cfg);
113
+
114
+ log.ok(`config: ${path.relative(root, paths.configFile(root))}`);
115
+
116
+ // Render workflow marker section (always)
117
+ const workflowBody = (await templates.load('ide/devflow-workflow.md')).trim();
118
+
119
+ // Render project overview marker section (mode-aware)
120
+ let overviewBody;
121
+ if (overviewMode === 'reference' && overviewSource) {
122
+ overviewBody = templates.render(
123
+ (await templates.load('ide/project-overview-reference.md')).trim(),
124
+ { overviewSource }
125
+ );
126
+ } else if (overviewMode === 'reference' && !overviewSource) {
127
+ overviewBody = `> 未检测到项目概览文件。可运行 \`df switch project-overview --mode=generate\` 让 devflow 生成,或手动创建 CLAUDE.md / PROJECT.md 后重跑 \`df sync project\`。`;
128
+ } else if (overviewMode === 'supplement' || overviewMode === 'generate') {
129
+ overviewBody = templates.render(
130
+ (await templates.load('ide/project-overview.md')).trim(),
131
+ {
132
+ overviewSource: overviewSource || '(none)',
133
+ stack: '(待填)',
134
+ frameworks: '(待填)',
135
+ packageManager: '(待填)',
136
+ modules: '- (待填)',
137
+ commands: '# 列出常用命令',
138
+ codingStyle: '- (待填)',
139
+ testingStyle: '- (待填)',
140
+ }
141
+ );
142
+ }
143
+
144
+ // IDE marker writes — gated by ideMode (see top of run() for rationale).
145
+ //
146
+ // full → every selected target gets workflow + project-overview marker
147
+ // minimal → no IDE files touched; one log line explains why
148
+ // none → silent skip
149
+ if (ideMode === 'full') {
150
+ for (const t of targets) {
151
+ const rel = IDE_TARGETS[t];
152
+ if (!rel) {
153
+ log.warn(`unknown ide target: ${t} (skip)`);
154
+ continue;
155
+ }
156
+ const file = path.join(root, rel);
157
+ if (overviewBody !== undefined) {
158
+ await markers.upsertSection(file, 'project-overview', overviewBody);
159
+ }
160
+ await markers.upsertSection(file, 'workflow', workflowBody);
161
+ log.ok(`ide: ${rel}`);
162
+ }
163
+ } else if (ideMode === 'minimal') {
164
+ log.ok('ide: minimal mode (skipped per-project IDE files)');
165
+ log.dim(' workflow content lives in user-wide skills: ~/.cursor/skills, ~/.claude/skills, ~/.agents/skills');
166
+ log.dim(` project overview is read from devflow/config.json#projectOverview.referencedFile${overviewSource ? ` (= ${overviewSource})` : ' (none detected)'}`);
167
+ log.dim(' override per-project: devflow init --ide=full');
168
+
169
+ // If a previous `devflow init --ide=full` already populated IDE files,
170
+ // hint the user that those marker sections are now redundant. We don't
171
+ // delete them automatically — the file may have other (non-devflow)
172
+ // content the user cares about.
173
+ const stale = [];
174
+ for (const t of targets) {
175
+ const rel = IDE_TARGETS[t];
176
+ if (!rel) continue;
177
+ const file = path.join(root, rel);
178
+ if (!fsSync.existsSync(file)) continue;
179
+ try {
180
+ const content = await fs.readFile(file, 'utf8');
181
+ if (content.includes('BEGIN devflow:workflow')) stale.push(rel);
182
+ } catch { /* ignore */ }
183
+ }
184
+ if (stale.length) {
185
+ log.dim(` note: ${stale.length} stale IDE file(s) from a previous --ide=full init: ${stale.join(', ')}`);
186
+ log.dim(' remove obsolete devflow markers via: devflow update --ide-clean');
187
+ }
188
+ } // ideMode === 'none' → silent skip
189
+
190
+ // .gitignore additions
191
+ await ensureGitignore(root);
192
+
193
+ // Optional CI templates — render with detected default branch so the
194
+ // generated YAML matches whatever the repo actually uses (main vs master).
195
+ const withCi = flags['with-ci'] || flags.withCi;
196
+ if (withCi) {
197
+ const tokens = String(withCi).split(',').map((s) => s.trim()).filter(Boolean);
198
+ const defaultBranch = (cfg.detect && cfg.detect.defaultBranch) || 'main';
199
+ for (const t of tokens) await installCi(root, t, { defaultBranch });
200
+ }
201
+
202
+ // Skill install policy:
203
+ // --no-skills : skip
204
+ // --skills-target=cursor,... : explicit project-local install
205
+ // (default) : if user-wide skills already exist, skip; otherwise per-project install
206
+ const skipSkills = flags['no-skills'] === true || flags.skills === false;
207
+ if (!skipSkills) {
208
+ const skillsTargetArg = flags['skills-target'] || flags.skillsTarget;
209
+ if (skillsTargetArg) {
210
+ const skillTargets = String(skillsTargetArg).split(',').map((s) => s.trim()).filter(Boolean);
211
+ try {
212
+ const summary = await skillsCmd.installForInit(root, skillTargets, { quiet: true });
213
+ for (const s of summary) log.ok(`skills: ${s.count} installed → ${s.path}`);
214
+ } catch (err) {
215
+ log.warn(`skills install failed: ${err.message} (run "devflow skills install" manually)`);
216
+ }
217
+ } else if (skillsCmd.userSkillsInstalled()) {
218
+ log.ok('skills: user-wide install detected (~/.cursor/skills, ~/.claude/skills, ~/.agents/skills) — skipping per-project copy');
219
+ log.dim(' (override with --skills-target=cursor,claude,agents)');
220
+ } else {
221
+ try {
222
+ const summary = await skillsCmd.installForInit(root, targets, { quiet: true });
223
+ for (const s of summary) log.ok(`skills: ${s.count} installed → ${s.path}`);
224
+ log.dim(' tip: run `devflow skills install --scope=user` once to share skills across all projects');
225
+ } catch (err) {
226
+ log.warn(`skills auto-install failed: ${err.message} (run "devflow skills install" manually)`);
227
+ }
228
+ }
229
+ }
230
+
231
+ log.raw('');
232
+ log.ok('done. next:');
233
+ // Surface the wizard prominently if no providers are configured yet — this is
234
+ // the most common "I just installed it, now what?" moment.
235
+ try {
236
+ const userCfgFile = paths.userProvidersFile();
237
+ let userCfg = {};
238
+ if (fsSync.existsSync(userCfgFile)) {
239
+ try { userCfg = JSON.parse(await fs.readFile(userCfgFile, 'utf8')); } catch { /* ignore */ }
240
+ }
241
+ if (Object.keys(userCfg).length === 0) {
242
+ log.dim(' devflow provider setup # ★ interactive: pick & configure providers');
243
+ }
244
+ } catch { /* ignore */ }
245
+ log.dim(' devflow ingest <wiki-url|issue-id> # primary entry');
246
+ log.dim(' devflow new <slug> # fallback (brainstorm)');
247
+ log.dim(' devflow status / devflow doctor # inspect');
248
+ }
249
+
250
+ async function ensureSkeleton(root) {
251
+ // Flat knowledge dirs: ~/.devflow/workspace/knowledge/<中文名>/ e.g. 架构决策/
252
+ const { CATEGORY_TAG_NAMES, resolveTagInfo } = require('../../knowledge/categories.js');
253
+ const knowledgeCatDirs = Object.values(CATEGORY_TAG_NAMES).map(
254
+ (info) => path.join(paths.workspaceKnowledgeRoot(), info.name),
255
+ );
256
+ const dirs = [
257
+ paths.devflowDir(root),
258
+ paths.specsDir(root),
259
+ paths.changesDir(root),
260
+ paths.archiveDir(root),
261
+ ...knowledgeCatDirs,
262
+ ];
263
+ for (const d of dirs) await fs.mkdir(d, { recursive: true });
264
+
265
+ // knowledge index.json
266
+ const idx = path.join(paths.workspaceKnowledgeRoot(), 'index.json');
267
+ if (!fsSync.existsSync(idx)) {
268
+ const tpl = await templates.load('knowledge-index.json');
269
+ await fs.writeFile(idx, tpl, 'utf8');
270
+ }
271
+
272
+ // .meta.json placeholders
273
+ const metaTpl = await templates.load('meta.json');
274
+ for (const d of knowledgeCatDirs) {
275
+ const meta = path.join(d, '.meta.json');
276
+ if (!fsSync.existsSync(meta)) {
277
+ const dirName = path.basename(d); // Chinese name, e.g. "架构决策"
278
+ await fs.writeFile(meta, templates.render(metaTpl, {
279
+ category: dirName,
280
+ tag_name: dirName,
281
+ }), 'utf8');
282
+ }
283
+ }
284
+ }
285
+
286
+ async function detectProjectOverview(root, candidates) {
287
+ for (const rel of candidates) {
288
+ const p = path.join(root, rel);
289
+ if (!fsSync.existsSync(p)) continue;
290
+ // skip if it's just a devflow-managed file
291
+ try {
292
+ const c = await fs.readFile(p, 'utf8');
293
+ if (c.includes('BEGIN devflow:project-overview') && !c.replace(/<!--[\s\S]*?-->/g, '').trim()) {
294
+ continue; // file is empty besides our markers
295
+ }
296
+ } catch { /* ignore */ }
297
+ return rel;
298
+ }
299
+ return null;
300
+ }
301
+
302
+ async function installCi(root, kind, opts = {}) {
303
+ const map = {
304
+ gh: { tpl: 'ci/github.yml', target: '.github/workflows/devflow.yml' },
305
+ github: { tpl: 'ci/github.yml', target: '.github/workflows/devflow.yml' },
306
+ gl: { tpl: 'ci/gitlab.yml', target: '.gitlab-ci.devflow.yml' },
307
+ gitlab: { tpl: 'ci/gitlab.yml', target: '.gitlab-ci.devflow.yml' },
308
+ };
309
+ const m = map[kind.toLowerCase()];
310
+ if (!m) { log.warn(`unknown ci target: ${kind} (skip)`); return; }
311
+ const targetAbs = path.join(root, m.target);
312
+ if (fsSync.existsSync(targetAbs)) { log.dim(`ci: ${m.target} (exists, skip)`); return; }
313
+ await fs.mkdir(path.dirname(targetAbs), { recursive: true });
314
+ const body = await templates.renderFile(m.tpl, {
315
+ defaultBranch: opts.defaultBranch || 'main',
316
+ });
317
+ await fs.writeFile(targetAbs, body, 'utf8');
318
+ log.ok(`ci: ${m.target} (branch=${opts.defaultBranch || 'main'})`);
319
+ }
320
+
321
+ async function ensureGitignore(root) {
322
+ const file = path.join(root, '.gitignore');
323
+ let content = '';
324
+ try { content = await fs.readFile(file, 'utf8'); } catch { /* new file */ }
325
+ const lines = content.split(/\r?\n/);
326
+ const want = [
327
+ '# devflow',
328
+ 'devflow/changes/*/refs/screenshots/',
329
+ 'devflow/.cache/',
330
+ ];
331
+ let changed = false;
332
+ for (const w of want) {
333
+ if (!lines.includes(w)) { lines.push(w); changed = true; }
334
+ }
335
+ if (changed) {
336
+ while (lines.length && lines[lines.length - 1] === '') lines.pop();
337
+ await fs.writeFile(file, lines.join('\n') + '\n', 'utf8');
338
+ }
339
+ }
340
+
341
+ module.exports = { run };