@clawplays/ospec-cli 0.1.1 → 0.3.0

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.
@@ -2463,7 +2463,23 @@ class ProjectService {
2463
2463
 
2464
2464
 
2465
2465
 
2466
- const execution = await this.getExecutionStatus(rootDir);
2466
+ let activeChangeCount = 0;
2467
+ if (structure.initialized) {
2468
+ const execution = await this.getExecutionStatus(rootDir);
2469
+ activeChangeCount = execution.totalActiveChanges;
2470
+ }
2471
+ else {
2472
+ const activeDir = path_1.default.join(rootDir, constants_1.DIR_NAMES.CHANGES, constants_1.DIR_NAMES.ACTIVE);
2473
+ if (await this.fileService.exists(activeDir)) {
2474
+ try {
2475
+ const entries = await fs_extra_1.default.readdir(activeDir, { withFileTypes: true });
2476
+ activeChangeCount = entries.filter(entry => entry.isDirectory()).length;
2477
+ }
2478
+ catch {
2479
+ activeChangeCount = 0;
2480
+ }
2481
+ }
2482
+ }
2467
2483
 
2468
2484
 
2469
2485
 
@@ -2527,7 +2543,7 @@ class ProjectService {
2527
2543
 
2528
2544
 
2529
2545
 
2530
- activeChangeCount: execution.totalActiveChanges,
2546
+ activeChangeCount,
2531
2547
 
2532
2548
 
2533
2549
 
@@ -5114,413 +5130,211 @@ class ProjectService {
5114
5130
 
5115
5131
 
5116
5132
  async getBootstrapUpgradePlan(rootDir) {
5117
-
5118
-
5119
-
5120
-
5121
-
5122
-
5123
-
5124
5133
  const docsRoot = path_1.default.join(rootDir, constants_1.DIR_NAMES.DOCS, constants_1.DIR_NAMES.PROJECT);
5125
-
5126
-
5127
-
5128
-
5129
-
5130
-
5131
-
5132
5134
  const readMarkdown = async (filePath) => {
5133
-
5134
-
5135
-
5136
-
5137
-
5138
-
5139
-
5140
5135
  if (!(await this.fileService.exists(filePath))) {
5141
-
5142
-
5143
-
5144
-
5145
-
5146
-
5147
-
5148
5136
  return '';
5149
-
5150
-
5151
-
5152
-
5153
-
5154
-
5155
-
5156
5137
  }
5157
-
5158
-
5159
-
5160
-
5161
-
5162
-
5163
-
5164
5138
  try {
5165
-
5166
-
5167
-
5168
-
5169
-
5170
-
5171
-
5172
5139
  const parsed = (0, gray_matter_1.default)(await this.fileService.readFile(filePath));
5173
-
5174
-
5175
-
5176
-
5177
-
5178
-
5179
-
5180
5140
  return parsed.content.trim();
5181
-
5182
-
5183
-
5184
-
5185
-
5186
-
5187
-
5188
5141
  }
5189
-
5190
-
5191
-
5192
-
5193
-
5194
-
5195
-
5196
5142
  catch {
5197
-
5198
-
5199
-
5200
-
5201
-
5202
-
5203
-
5204
5143
  return '';
5205
-
5206
-
5207
-
5208
-
5209
-
5210
-
5211
-
5212
5144
  }
5213
-
5214
-
5215
-
5216
-
5217
-
5218
-
5219
-
5220
5145
  };
5221
-
5222
-
5223
-
5224
-
5225
-
5226
-
5227
-
5228
5146
  const extractBulletList = (content) => content
5229
-
5230
-
5231
-
5232
-
5233
-
5234
-
5235
-
5236
5147
  .split(/\r?\n/)
5237
-
5238
-
5239
-
5240
-
5241
-
5242
-
5243
-
5244
5148
  .map(line => line.trim())
5245
-
5246
-
5247
-
5248
-
5249
-
5250
-
5251
-
5252
5149
  .filter(line => /^-\s+/.test(line))
5253
-
5254
-
5255
-
5256
-
5257
-
5258
-
5259
-
5260
5150
  .map(line => line.replace(/^-\s+/, '').trim())
5261
-
5262
-
5263
-
5264
-
5265
-
5266
-
5267
-
5268
5151
  .filter(Boolean);
5269
-
5270
-
5271
-
5272
-
5273
-
5274
-
5275
-
5276
5152
  const extractParagraph = (content) => content
5277
-
5278
-
5279
-
5280
-
5281
-
5282
-
5283
-
5284
5153
  .split(/\r?\n\r?\n/)
5285
-
5286
-
5287
-
5288
-
5289
-
5290
-
5291
-
5292
5154
  .map(block => block.trim())
5293
-
5294
-
5295
-
5296
-
5297
-
5298
-
5299
-
5300
5155
  .find(block => block && !block.startsWith('#') && !block.startsWith('- '))
5301
-
5302
-
5303
-
5304
-
5305
-
5306
-
5307
-
5308
5156
  ?.replace(/\r?\n/g, ' ')
5309
-
5310
-
5311
-
5312
-
5313
-
5314
-
5315
-
5316
5157
  .trim() || '';
5317
-
5318
-
5319
-
5320
-
5321
-
5322
-
5323
-
5324
- const [overviewContent, techStackContent, architectureContent] = await Promise.all([
5325
-
5326
-
5327
-
5328
-
5329
-
5330
-
5331
-
5158
+ const [overviewContent, techStackContent, architectureContent, readmeContent, localizedReadmeContent, inferredSummary, inferredTechStack] = await Promise.all([
5332
5159
  readMarkdown(path_1.default.join(docsRoot, 'overview.md')),
5333
-
5334
-
5335
-
5336
-
5337
-
5338
-
5339
-
5340
5160
  readMarkdown(path_1.default.join(docsRoot, 'tech-stack.md')),
5341
-
5342
-
5343
-
5344
-
5345
-
5346
-
5347
-
5348
5161
  readMarkdown(path_1.default.join(docsRoot, 'architecture.md')),
5349
-
5350
-
5351
-
5352
-
5353
-
5354
-
5355
-
5162
+ readMarkdown(path_1.default.join(rootDir, constants_1.FILE_NAMES.README)),
5163
+ readMarkdown(path_1.default.join(rootDir, 'README.zh-CN.md')),
5164
+ this.inferBootstrapSummary(rootDir),
5165
+ this.inferBootstrapTechStack(rootDir),
5166
+ ]);
5167
+ const summary = extractParagraph(overviewContent) ||
5168
+ extractParagraph(readmeContent) ||
5169
+ extractParagraph(localizedReadmeContent) ||
5170
+ inferredSummary;
5171
+ const explicitTechStack = extractBulletList(techStackContent);
5172
+ const documentLanguage = this.detectDocumentLanguageFromTexts([
5173
+ overviewContent,
5174
+ techStackContent,
5175
+ architectureContent,
5176
+ readmeContent,
5177
+ localizedReadmeContent,
5356
5178
  ]);
5357
-
5358
-
5359
-
5360
-
5361
-
5362
-
5363
-
5364
5179
  const modules = await this.inferBootstrapModules(rootDir);
5365
-
5366
-
5367
-
5368
-
5369
-
5370
-
5371
-
5372
5180
  const apiDocs = await this.scanApiDocs(rootDir);
5373
-
5374
-
5375
-
5376
-
5377
-
5378
-
5379
-
5380
5181
  const designDocs = await this.scanDesignDocs(rootDir);
5381
-
5382
-
5383
-
5384
-
5385
-
5386
-
5387
-
5388
5182
  const planningDocs = await this.scanPlanningDocs(rootDir);
5389
-
5390
-
5391
-
5392
-
5393
-
5394
-
5395
-
5396
5183
  return {
5397
-
5398
-
5399
-
5400
-
5401
-
5402
-
5403
-
5404
5184
  projectName: path_1.default.basename(path_1.default.resolve(rootDir)),
5405
-
5406
-
5407
-
5408
-
5409
-
5410
-
5411
-
5412
- summary: extractParagraph(overviewContent),
5413
-
5414
-
5415
-
5416
-
5417
-
5418
-
5419
-
5420
- techStack: extractBulletList(techStackContent),
5421
-
5422
-
5423
-
5424
-
5425
-
5426
-
5427
-
5185
+ summary,
5186
+ techStack: explicitTechStack.length > 0 ? explicitTechStack : inferredTechStack,
5428
5187
  architecture: extractParagraph(architectureContent),
5429
-
5430
-
5431
-
5432
-
5433
-
5434
-
5435
-
5436
5188
  modules,
5437
-
5438
-
5439
-
5440
-
5441
-
5442
-
5443
-
5444
5189
  apiAreas: apiDocs
5445
-
5446
-
5447
-
5448
-
5449
-
5450
-
5451
-
5452
5190
  .filter(item => item.name.toLowerCase() !== 'readme.md')
5453
-
5454
-
5455
-
5456
-
5457
-
5458
-
5459
-
5460
5191
  .map(item => item.name.replace(/\.md$/i, '').replace(/-/g, ' ')),
5461
-
5462
-
5463
-
5464
-
5465
-
5466
-
5467
-
5468
5192
  designDocs: designDocs
5469
-
5470
-
5471
-
5472
-
5473
-
5474
-
5475
-
5476
5193
  .filter(item => item.name.toLowerCase() !== 'readme.md')
5477
-
5478
-
5479
-
5480
-
5481
-
5482
-
5483
-
5484
5194
  .map(item => item.name.replace(/\.md$/i, '').replace(/-/g, ' ')),
5485
-
5486
-
5487
-
5488
-
5489
-
5490
-
5491
-
5492
5195
  planningDocs: planningDocs
5493
-
5494
-
5495
-
5496
-
5497
-
5498
-
5499
-
5500
5196
  .filter(item => item.name.toLowerCase() !== 'readme.md')
5501
-
5502
-
5503
-
5504
-
5505
-
5506
-
5507
-
5508
5197
  .map(item => item.name.replace(/\.md$/i, '').replace(/-/g, ' ')),
5509
-
5510
-
5511
-
5512
-
5513
-
5514
-
5515
-
5198
+ documentLanguage,
5516
5199
  };
5200
+ }
5517
5201
 
5202
+ async inferBootstrapSummary(rootDir) {
5203
+ const packageJsonPath = path_1.default.join(rootDir, 'package.json');
5204
+ if (await this.fileService.exists(packageJsonPath)) {
5205
+ try {
5206
+ const packageJson = await this.fileService.readJSON(packageJsonPath);
5207
+ if (typeof packageJson?.description === 'string' && packageJson.description.trim().length > 0) {
5208
+ return packageJson.description.trim();
5209
+ }
5210
+ }
5211
+ catch {
5212
+ }
5213
+ }
5214
+ const pyprojectPath = path_1.default.join(rootDir, 'pyproject.toml');
5215
+ if (await this.fileService.exists(pyprojectPath)) {
5216
+ try {
5217
+ const content = await this.fileService.readFile(pyprojectPath);
5218
+ const match = content.match(/^\s*description\s*=\s*["'](.+?)["']\s*$/m);
5219
+ if (match?.[1]?.trim()) {
5220
+ return match[1].trim();
5221
+ }
5222
+ }
5223
+ catch {
5224
+ }
5225
+ }
5226
+ return '';
5227
+ }
5518
5228
 
5229
+ async inferBootstrapTechStack(rootDir) {
5230
+ const stack = new Set();
5231
+ const add = (...items) => {
5232
+ for (const item of items) {
5233
+ if (typeof item === 'string' && item.trim().length > 0) {
5234
+ stack.add(item.trim());
5235
+ }
5236
+ }
5237
+ };
5238
+ const packageJsonPath = path_1.default.join(rootDir, 'package.json');
5239
+ if (await this.fileService.exists(packageJsonPath)) {
5240
+ add('Node.js');
5241
+ try {
5242
+ const packageJson = await this.fileService.readJSON(packageJsonPath);
5243
+ const deps = {
5244
+ ...(packageJson?.dependencies || {}),
5245
+ ...(packageJson?.devDependencies || {}),
5246
+ ...(packageJson?.peerDependencies || {}),
5247
+ };
5248
+ const depNames = Object.keys(deps);
5249
+ if (depNames.some(name => name === 'typescript' || name.startsWith('@types/'))) {
5250
+ add('TypeScript');
5251
+ }
5252
+ if (depNames.includes('react')) {
5253
+ add('React');
5254
+ }
5255
+ if (depNames.includes('next')) {
5256
+ add('Next.js');
5257
+ }
5258
+ if (depNames.includes('vue')) {
5259
+ add('Vue');
5260
+ }
5261
+ if (depNames.includes('nuxt') || depNames.includes('nuxt3')) {
5262
+ add('Nuxt');
5263
+ }
5264
+ if (depNames.includes('svelte')) {
5265
+ add('Svelte');
5266
+ }
5267
+ if (depNames.includes('astro')) {
5268
+ add('Astro');
5269
+ }
5270
+ if (depNames.includes('express')) {
5271
+ add('Express');
5272
+ }
5273
+ if (depNames.includes('@nestjs/core')) {
5274
+ add('NestJS');
5275
+ }
5276
+ if (depNames.includes('fastify')) {
5277
+ add('Fastify');
5278
+ }
5279
+ if (depNames.includes('electron')) {
5280
+ add('Electron');
5281
+ }
5282
+ if (depNames.includes('vite')) {
5283
+ add('Vite');
5284
+ }
5285
+ }
5286
+ catch {
5287
+ }
5288
+ }
5289
+ if (await this.fileService.exists(path_1.default.join(rootDir, 'tsconfig.json'))) {
5290
+ add('TypeScript');
5291
+ }
5292
+ if (await this.fileService.exists(path_1.default.join(rootDir, 'requirements.txt')) ||
5293
+ await this.fileService.exists(path_1.default.join(rootDir, 'pyproject.toml'))) {
5294
+ add('Python');
5295
+ }
5296
+ if (await this.fileService.exists(path_1.default.join(rootDir, 'manage.py'))) {
5297
+ add('Django');
5298
+ }
5299
+ if (await this.fileService.exists(path_1.default.join(rootDir, 'go.mod'))) {
5300
+ add('Go');
5301
+ }
5302
+ if (await this.fileService.exists(path_1.default.join(rootDir, 'Cargo.toml'))) {
5303
+ add('Rust');
5304
+ }
5305
+ if (await this.fileService.exists(path_1.default.join(rootDir, 'composer.json'))) {
5306
+ add('PHP');
5307
+ }
5308
+ if (await this.fileService.exists(path_1.default.join(rootDir, 'Gemfile'))) {
5309
+ add('Ruby');
5310
+ }
5311
+ if (await this.fileService.exists(path_1.default.join(rootDir, 'pom.xml')) ||
5312
+ await this.fileService.exists(path_1.default.join(rootDir, 'build.gradle')) ||
5313
+ await this.fileService.exists(path_1.default.join(rootDir, 'build.gradle.kts'))) {
5314
+ add('Java');
5315
+ }
5316
+ if (await this.fileService.exists(path_1.default.join(rootDir, 'pubspec.yaml'))) {
5317
+ add('Dart');
5318
+ }
5319
+ if (await this.fileService.exists(path_1.default.join(rootDir, 'Dockerfile'))) {
5320
+ add('Docker');
5321
+ }
5322
+ return Array.from(stack);
5323
+ }
5519
5324
 
5520
-
5521
-
5522
-
5523
-
5325
+ detectDocumentLanguageFromTexts(contents) {
5326
+ for (const content of contents) {
5327
+ if (typeof content !== 'string' || content.trim().length === 0) {
5328
+ continue;
5329
+ }
5330
+ if (/[一-龥]/.test(content)) {
5331
+ return 'zh-CN';
5332
+ }
5333
+ if (/[A-Za-z]/.test(content)) {
5334
+ return 'en-US';
5335
+ }
5336
+ }
5337
+ return undefined;
5524
5338
  }
5525
5339
 
5526
5340
 
@@ -142,7 +142,6 @@ ${context.architecture}
142
142
  ## Navigation
143
143
 
144
144
  - Project overview: [project/overview.md](project/overview.md)
145
- - Bootstrap summary: [project/bootstrap-summary.md](project/bootstrap-summary.md)
146
145
  - Tech stack: [project/tech-stack.md](project/tech-stack.md)
147
146
  - Architecture: [project/architecture.md](project/architecture.md)
148
147
  - Module map: [project/module-map.md](project/module-map.md)
@@ -157,7 +156,6 @@ ${context.architecture}
157
156
  ## 文档导航
158
157
 
159
158
  - 项目概览:[project/overview.md](project/overview.md)
160
- - 初始化摘要:[project/bootstrap-summary.md](project/bootstrap-summary.md)
161
159
  - 技术栈:[project/tech-stack.md](project/tech-stack.md)
162
160
  - 架构说明:[project/architecture.md](project/architecture.md)
163
161
  - 模块地图:[project/module-map.md](project/module-map.md)
@@ -283,8 +281,7 @@ ${context.summary}
283
281
 
284
282
  ## Current Goals
285
283
 
286
- - Complete the project knowledge layer
287
- - Use [bootstrap-summary.md](bootstrap-summary.md) as the recorded bootstrap output
284
+ - Keep the project knowledge docs aligned with the real repository state
288
285
  - Establish layered skill files
289
286
  - Manage delivery through the change workflow`
290
287
  : `# 项目概览
@@ -299,8 +296,7 @@ ${context.summary}
299
296
 
300
297
  ## 当前目标
301
298
 
302
- - 补齐项目知识层
303
- - 以 [bootstrap-summary.md](bootstrap-summary.md) 记录初始化结果与脚手架输出
299
+ - 保持项目知识文档与仓库真实状态一致
304
300
  - 建立分层 SKILL 体系
305
301
  - 使用 change workflow 管理需求交付`;
306
302
  return this.withFrontmatter({
@@ -1008,11 +1004,9 @@ scan(rootDir)
1008
1004
  const docsLink = this.formatReferenceList(refs, this.copy(context.documentLanguage, '待补充', 'TBD'));
1009
1005
  const relatedDocs = this.isEnglish(context.documentLanguage)
1010
1006
  ? `- Module map: [../../../docs/project/module-map.md](../../../docs/project/module-map.md)
1011
- - API overview: [../../../docs/project/api-overview.md](../../../docs/project/api-overview.md)
1012
- - Bootstrap summary: [../../../docs/project/bootstrap-summary.md](../../../docs/project/bootstrap-summary.md)`
1007
+ - API overview: [../../../docs/project/api-overview.md](../../../docs/project/api-overview.md)`
1013
1008
  : `- 项目模块地图:[../../../docs/project/module-map.md](../../../docs/project/module-map.md)
1014
- - API 总览:[../../../docs/project/api-overview.md](../../../docs/project/api-overview.md)
1015
- - 初始化摘要:[../../../docs/project/bootstrap-summary.md](../../../docs/project/bootstrap-summary.md)`;
1009
+ - API 总览:[../../../docs/project/api-overview.md](../../../docs/project/api-overview.md)`;
1016
1010
  const templates = {
1017
1011
  web: {
1018
1012
  zh: `# ${moduleName} 模块
@@ -89,20 +89,28 @@ class TemplateInputFactory {
89
89
  const presetPlanningDocs = (presetDefaults?.planningDocs ?? []).map(item => item.trim()).filter(Boolean);
90
90
  const presetSummary = presetDefaults?.summary?.trim();
91
91
  const presetArchitecture = presetDefaults?.architecture?.trim();
92
+ const documentLanguage = this.normalizeDocumentLanguage(input?.documentLanguage);
93
+ const placeholderText = documentLanguage === 'en-US' ? 'TBD' : '待补充';
94
+ const defaultSummary = documentLanguage === 'en-US'
95
+ ? `${fallbackName} has been initialized with OSpec and is ready for change-based delivery. Fill in the missing project context as the repository becomes clearer.`
96
+ : `${fallbackName} 已通过 OSpec 初始化,当前已具备按 change 推进交付的基础条件。请继续补充缺失的项目上下文。`;
97
+ const defaultArchitecture = documentLanguage === 'en-US'
98
+ ? 'OSpec initialized the protocol shell and baseline project knowledge docs. Refine the architecture details as the repository direction becomes clearer.'
99
+ : 'OSpec 已初始化协议壳和基础项目知识文档,后续可结合仓库实际方向继续细化架构。';
92
100
  const projectName = normalizedInputProjectName || fallbackName;
93
101
  const summary = normalizedInputSummary ||
94
102
  presetSummary ||
95
- `${fallbackName} 已通过 OSpec 初始化,当前处于项目知识层搭建阶段。`;
103
+ defaultSummary;
96
104
  const techStack = normalizedInputTechStack.length > 0
97
105
  ? Array.from(new Set(normalizedInputTechStack))
98
106
  : inferredTechStack.length > 0
99
107
  ? Array.from(new Set(inferredTechStack))
100
108
  : presetTechStack.length > 0
101
109
  ? Array.from(new Set(presetTechStack))
102
- : ['待补充'];
110
+ : [placeholderText];
103
111
  const architecture = normalizedInputArchitecture ||
104
112
  presetArchitecture ||
105
- '当前先建立项目知识层、分层技能层和执行层骨架,后续继续细化架构。';
113
+ defaultArchitecture;
106
114
  const modules = normalizedInputModules.length > 0
107
115
  ? Array.from(new Set(normalizedInputModules))
108
116
  : inferredModules.length > 0
@@ -116,22 +124,21 @@ class TemplateInputFactory {
116
124
  ? Array.from(new Set(inferredApiAreas))
117
125
  : presetApiAreas.length > 0
118
126
  ? Array.from(new Set(presetApiAreas))
119
- : ['待补充'];
127
+ : [placeholderText];
120
128
  const designDocs = normalizedInputDesignDocs.length > 0
121
129
  ? Array.from(new Set(normalizedInputDesignDocs))
122
130
  : inferredDesignDocs.length > 0
123
131
  ? Array.from(new Set(inferredDesignDocs))
124
132
  : presetDesignDocs.length > 0
125
133
  ? Array.from(new Set(presetDesignDocs))
126
- : ['待补充'];
134
+ : [placeholderText];
127
135
  const planningDocs = normalizedInputPlanningDocs.length > 0
128
136
  ? Array.from(new Set(normalizedInputPlanningDocs))
129
137
  : inferredPlanningDocs.length > 0
130
138
  ? Array.from(new Set(inferredPlanningDocs))
131
139
  : presetPlanningDocs.length > 0
132
140
  ? Array.from(new Set(presetPlanningDocs))
133
- : ['待补充'];
134
- const documentLanguage = this.normalizeDocumentLanguage(input?.documentLanguage);
141
+ : [placeholderText];
135
142
  const executeScaffoldCommands = Boolean(input?.executeScaffoldCommands);
136
143
  const fieldSources = {
137
144
  projectName: normalizedInputProjectName ? 'user' : 'placeholder',