@clawplays/ospec-cli 0.3.7 → 0.3.8

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.
@@ -4545,6 +4545,7 @@ class ProjectService {
4545
4545
 
4546
4546
 
4547
4547
 
4548
+ await this.rebaseMovedChangeMarkdownLinks(resolvedFeaturePath, archivePath);
4548
4549
  await this.rebuildIndex(projectRoot);
4549
4550
 
4550
4551
 
@@ -4593,6 +4594,51 @@ class ProjectService {
4593
4594
 
4594
4595
 
4595
4596
 
4597
+ async rebaseMovedChangeMarkdownLinks(previousChangePath, nextChangePath) {
4598
+ const previousRoot = path_1.default.resolve(previousChangePath);
4599
+ const nextRoot = path_1.default.resolve(nextChangePath);
4600
+ const markdownFiles = [
4601
+ constants_1.FILE_NAMES.PROPOSAL,
4602
+ constants_1.FILE_NAMES.TASKS,
4603
+ constants_1.FILE_NAMES.VERIFICATION,
4604
+ constants_1.FILE_NAMES.REVIEW,
4605
+ ];
4606
+ for (const fileName of markdownFiles) {
4607
+ const nextFilePath = path_1.default.join(nextRoot, fileName);
4608
+ if (!(await this.fileService.exists(nextFilePath))) {
4609
+ continue;
4610
+ }
4611
+ const previousFilePath = path_1.default.join(previousRoot, fileName);
4612
+ const originalContent = await this.fileService.readFile(nextFilePath);
4613
+ const previousDir = path_1.default.dirname(previousFilePath);
4614
+ const nextDir = path_1.default.dirname(nextFilePath);
4615
+ const rewrittenContent = originalContent.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (match, label, rawHref) => {
4616
+ const href = String(rawHref || '').trim();
4617
+ if (!this.isRelativeMarkdownHref(href)) {
4618
+ return match;
4619
+ }
4620
+ const previousTargetPath = path_1.default.resolve(previousDir, href);
4621
+ if (this.isPathWithin(previousTargetPath, previousRoot)) {
4622
+ return match;
4623
+ }
4624
+ const rebasedHref = path_1.default.relative(nextDir, previousTargetPath).replace(/\\/g, '/');
4625
+ return `[${label}](${rebasedHref || '.'})`;
4626
+ });
4627
+ if (rewrittenContent !== originalContent) {
4628
+ await this.fileService.writeFile(nextFilePath, rewrittenContent);
4629
+ }
4630
+ }
4631
+ }
4632
+ isRelativeMarkdownHref(href) {
4633
+ return Boolean(href) &&
4634
+ !href.startsWith('#') &&
4635
+ !href.startsWith('//') &&
4636
+ !/^[a-z][a-z0-9+.-]*:/i.test(href);
4637
+ }
4638
+ isPathWithin(targetPath, parentPath) {
4639
+ const relativePath = path_1.default.relative(parentPath, targetPath);
4640
+ return relativePath === '' || (!relativePath.startsWith('..') && !path_1.default.isAbsolute(relativePath));
4641
+ }
4596
4642
  async getFeatureProjectContext(rootDir, affects = []) {
4597
4643
 
4598
4644
 
@@ -5275,7 +5321,8 @@ class ProjectService {
5275
5321
  }
5276
5322
  async syncConfigDocumentLanguage(rootDir, config, documentLanguage) {
5277
5323
  const normalized = this.normalizeDocumentLanguage(documentLanguage);
5278
- if (!normalized || config?.documentLanguage === normalized) {
5324
+ const configured = this.normalizeDocumentLanguage(config?.documentLanguage);
5325
+ if (!normalized || configured) {
5279
5326
  return false;
5280
5327
  }
5281
5328
  config.documentLanguage = normalized;
@@ -5284,13 +5331,28 @@ class ProjectService {
5284
5331
  }
5285
5332
 
5286
5333
  detectDocumentLanguageFromTexts(contents) {
5287
- for (const content of contents) {
5334
+ const detectionCounts = new Map();
5335
+ const firstSeenOrder = new Map();
5336
+ for (const [index, content] of contents.entries()) {
5288
5337
  const detected = this.detectDocumentLanguageFromText(content);
5289
5338
  if (detected) {
5290
- return detected;
5339
+ detectionCounts.set(detected, (detectionCounts.get(detected) || 0) + 1);
5340
+ if (!firstSeenOrder.has(detected)) {
5341
+ firstSeenOrder.set(detected, index);
5342
+ }
5291
5343
  }
5292
5344
  }
5293
- return undefined;
5345
+ if (detectionCounts.size === 0) {
5346
+ return undefined;
5347
+ }
5348
+ return Array.from(detectionCounts.entries())
5349
+ .sort((left, right) => {
5350
+ if (right[1] !== left[1]) {
5351
+ return right[1] - left[1];
5352
+ }
5353
+ return (firstSeenOrder.get(left[0]) ?? Number.MAX_SAFE_INTEGER) -
5354
+ (firstSeenOrder.get(right[0]) ?? Number.MAX_SAFE_INTEGER);
5355
+ })[0][0];
5294
5356
  }
5295
5357
  detectDocumentLanguageFromText(content) {
5296
5358
  if (typeof content !== 'string' || content.trim().length === 0) {
@@ -5299,28 +5361,28 @@ class ProjectService {
5299
5361
  if (/[\u0600-\u06FF]/.test(content)) {
5300
5362
  return 'ar';
5301
5363
  }
5302
- if (/[ぁ-ゟ゠-ヿ]/.test(content)) {
5303
- return 'ja-JP';
5304
- }
5305
- if (this.isLikelyJapaneseKanjiContent(content)) {
5364
+ if (this.hasJapaneseKana(content)) {
5306
5365
  return 'ja-JP';
5307
5366
  }
5308
- if (/[一-龥]/.test(content)) {
5309
- return 'zh-CN';
5367
+ if (this.hasCjkIdeographs(content)) {
5368
+ return this.isLikelyJapaneseKanjiContent(content) ? 'ja-JP' : 'zh-CN';
5310
5369
  }
5311
5370
  if (/[A-Za-z]/.test(content)) {
5312
5371
  return 'en-US';
5313
5372
  }
5314
5373
  return undefined;
5315
5374
  }
5375
+ hasJapaneseKana(content) {
5376
+ return /[\u3040-\u30FF]/.test(content);
5377
+ }
5378
+ hasCjkIdeographs(content) {
5379
+ return /[\u3400-\u9FFF]/.test(content);
5380
+ }
5316
5381
  isLikelyJapaneseKanjiContent(content) {
5317
- if (!/[一-龥]/.test(content)) {
5382
+ if (!this.hasCjkIdeographs(content)) {
5318
5383
  return false;
5319
5384
  }
5320
- if (/[々〆ヵヶ「」『』]/.test(content)) {
5321
- return true;
5322
- }
5323
- return /(一覧|詳細|設定|権限|検索|構成|変更|確認|対応|連携|承認|申請|手順|履歴|機能|実装|設計|運用|画面|帳票|組織|拠点|区分|種別|完了|開始|終了|表示|取得|追加|削除|更新|登録)/.test(content);
5385
+ return /[\u3005\u3006\u300C-\u300F\u30F5\u30F6]/.test(content);
5324
5386
  }
5325
5387
 
5326
5388
 
@@ -11034,197 +11096,103 @@ ${formatSuggestion()}
11034
11096
 
11035
11097
 
11036
11098
  const content = await this.fileService.readFile(filePath);
11037
-
11038
-
11039
-
11040
-
11041
-
11042
-
11043
-
11044
- const parsed = (0, gray_matter_1.default)(content);
11045
-
11046
-
11047
-
11048
-
11049
-
11050
-
11051
-
11052
- const optionalSteps = Array.isArray(parsed.data.optional_steps) ? parsed.data.optional_steps : [];
11053
-
11054
-
11055
-
11056
-
11057
-
11058
-
11059
-
11060
- const missing = activatedSteps.filter(step => !optionalSteps.includes(step));
11061
-
11062
-
11063
-
11064
-
11065
-
11066
-
11067
-
11068
- const checklistComplete = !/- \[ \]/.test(content);
11069
-
11070
-
11071
-
11072
-
11073
-
11074
-
11075
-
11099
+ const hasFrontmatter = /^---\r?\n[\s\S]*?\r?\n---(?:\r?\n|$)/.test(content);
11100
+ let parsed = null;
11101
+ let parseError = null;
11102
+ if (hasFrontmatter) {
11103
+ try {
11104
+ parsed = (0, gray_matter_1.default)(content);
11105
+ }
11106
+ catch (error) {
11107
+ parseError = error;
11108
+ }
11109
+ }
11110
+ const data = parsed?.data ?? {};
11111
+ const optionalStepsFieldValid = Array.isArray(data.optional_steps);
11112
+ const optionalSteps = optionalStepsFieldValid ? data.optional_steps : [];
11113
+ const createdFieldValid = (typeof data.created === 'string' && data.created.trim().length > 0) ||
11114
+ (data.created instanceof Date && !Number.isNaN(data.created.getTime()));
11115
+ const missingRequiredFields = [];
11116
+ if (typeof data.feature !== 'string' || data.feature.trim().length === 0) {
11117
+ missingRequiredFields.push('feature');
11118
+ }
11119
+ if (!createdFieldValid) {
11120
+ missingRequiredFields.push('created');
11121
+ }
11122
+ if (!optionalStepsFieldValid) {
11123
+ missingRequiredFields.push('optional_steps');
11124
+ }
11125
+ const missing = optionalStepsFieldValid
11126
+ ? activatedSteps.filter(step => !optionalSteps.includes(step))
11127
+ : [...activatedSteps];
11128
+ const checklistItems = parsed?.content.match(/^\s*-\s+\[(?: |x|X)\]\s+.+$/gm) ?? [];
11129
+ const uncheckedItems = parsed?.content.match(/^\s*-\s+\[ \]\s+.+$/gm) ?? [];
11130
+ const checklistStructureValid = checklistItems.length > 0;
11131
+ const checklistComplete = hasFrontmatter &&
11132
+ parseError === null &&
11133
+ missingRequiredFields.length === 0 &&
11134
+ checklistStructureValid &&
11135
+ uncheckedItems.length === 0;
11136
+ let frontmatterMessage = `${name} frontmatter parsed successfully`;
11137
+ if (!hasFrontmatter) {
11138
+ frontmatterMessage = `${name} is missing a valid frontmatter block`;
11139
+ }
11140
+ else if (parseError) {
11141
+ frontmatterMessage = `${name} frontmatter cannot be parsed: ${parseError.message}`;
11142
+ }
11143
+ let requiredFieldsMessage = `${name} has all required frontmatter fields`;
11144
+ if (!hasFrontmatter || parseError) {
11145
+ requiredFieldsMessage = `Cannot validate required fields in ${name} because frontmatter is invalid`;
11146
+ }
11147
+ else if (missingRequiredFields.length > 0) {
11148
+ requiredFieldsMessage = `Missing or invalid required fields in ${name}: ${missingRequiredFields.join(', ')}`;
11149
+ }
11150
+ let optionalStepsMessage = `All activated optional steps are present in ${name}`;
11151
+ if (!optionalStepsFieldValid) {
11152
+ optionalStepsMessage = `${name} frontmatter field optional_steps must be an array`;
11153
+ }
11154
+ else if (missing.length > 0) {
11155
+ optionalStepsMessage = `Missing optional steps in ${name}: ${missing.join(', ')}`;
11156
+ }
11157
+ let checklistStatus = 'pass';
11158
+ let checklistMessage = `${name} checklist is complete`;
11159
+ if (!hasFrontmatter || parseError) {
11160
+ checklistStatus = 'fail';
11161
+ checklistMessage = `${name} checklist cannot be validated because frontmatter is invalid`;
11162
+ }
11163
+ else if (!checklistStructureValid) {
11164
+ checklistStatus = 'fail';
11165
+ checklistMessage = `${name} must contain at least one Markdown checklist item`;
11166
+ }
11167
+ else if (uncheckedItems.length > 0) {
11168
+ checklistStatus = 'warn';
11169
+ checklistMessage = `${name} still has unchecked items`;
11170
+ }
11076
11171
  return {
11077
-
11078
-
11079
-
11080
-
11081
-
11082
-
11083
-
11084
11172
  optionalSteps,
11085
-
11086
-
11087
-
11088
-
11089
-
11090
-
11091
-
11092
11173
  checklistComplete,
11093
-
11094
-
11095
-
11096
-
11097
-
11098
-
11099
-
11100
11174
  checks: [
11101
-
11102
-
11103
-
11104
-
11105
-
11106
-
11107
-
11108
11175
  {
11109
-
11110
-
11111
-
11112
-
11113
-
11114
-
11115
-
11176
+ name: `${name}.frontmatter`,
11177
+ status: hasFrontmatter && parseError === null ? 'pass' : 'fail',
11178
+ message: frontmatterMessage,
11179
+ },
11180
+ {
11181
+ name: `${name}.required_fields`,
11182
+ status: hasFrontmatter && parseError === null && missingRequiredFields.length === 0 ? 'pass' : 'fail',
11183
+ message: requiredFieldsMessage,
11184
+ },
11185
+ {
11116
11186
  name: `${name}.optional_steps`,
11117
-
11118
-
11119
-
11120
-
11121
-
11122
-
11123
-
11124
- status: missing.length === 0 ? 'pass' : 'fail',
11125
-
11126
-
11127
-
11128
-
11129
-
11130
-
11131
-
11132
- message: missing.length === 0
11133
-
11134
-
11135
-
11136
-
11137
-
11138
-
11139
-
11140
- ? `All activated optional steps are present in ${name}`
11141
-
11142
-
11143
-
11144
-
11145
-
11146
-
11147
-
11148
- : `Missing optional steps in ${name}: ${missing.join(', ')}`,
11149
-
11150
-
11151
-
11152
-
11153
-
11154
-
11155
-
11187
+ status: optionalStepsFieldValid && missing.length === 0 ? 'pass' : 'fail',
11188
+ message: optionalStepsMessage,
11156
11189
  },
11157
-
11158
-
11159
-
11160
-
11161
-
11162
-
11163
-
11164
11190
  {
11165
-
11166
-
11167
-
11168
-
11169
-
11170
-
11171
-
11172
11191
  name: `${name}.checklist`,
11173
-
11174
-
11175
-
11176
-
11177
-
11178
-
11179
-
11180
- status: checklistComplete ? 'pass' : 'warn',
11181
-
11182
-
11183
-
11184
-
11185
-
11186
-
11187
-
11188
- message: checklistComplete
11189
-
11190
-
11191
-
11192
-
11193
-
11194
-
11195
-
11196
- ? `${name} checklist is complete`
11197
-
11198
-
11199
-
11200
-
11201
-
11202
-
11203
-
11204
- : `${name} still has unchecked items`,
11205
-
11206
-
11207
-
11208
-
11209
-
11210
-
11211
-
11192
+ status: checklistStatus,
11193
+ message: checklistMessage,
11212
11194
  },
11213
-
11214
-
11215
-
11216
-
11217
-
11218
-
11219
-
11220
11195
  ],
11221
-
11222
-
11223
-
11224
-
11225
-
11226
-
11227
-
11228
11196
  };
11229
11197
 
11230
11198
 
@@ -11250,229 +11218,112 @@ ${formatSuggestion()}
11250
11218
 
11251
11219
 
11252
11220
  const content = await this.fileService.readFile(filePath);
11253
-
11254
-
11255
-
11256
-
11257
-
11258
-
11259
-
11260
- const parsed = (0, gray_matter_1.default)(content);
11261
-
11262
-
11263
-
11264
-
11265
-
11266
-
11267
-
11268
- const optionalSteps = Array.isArray(parsed.data.optional_steps) ? parsed.data.optional_steps : [];
11269
-
11270
-
11271
-
11272
-
11273
-
11274
-
11275
-
11276
- const passedOptionalSteps = Array.isArray(parsed.data.passed_optional_steps)
11277
-
11278
-
11279
-
11280
-
11281
-
11282
-
11283
-
11284
- ? parsed.data.passed_optional_steps
11285
-
11286
-
11287
-
11288
-
11289
-
11290
-
11291
-
11292
- : [];
11293
-
11294
-
11295
-
11296
-
11297
-
11298
-
11299
-
11300
- const missing = activatedSteps.filter(step => !optionalSteps.includes(step));
11301
-
11302
-
11303
-
11304
-
11305
-
11306
-
11307
-
11308
- const checklistComplete = !/- \[ \]/.test(content);
11309
-
11310
-
11311
-
11312
-
11313
-
11314
-
11315
-
11221
+ const hasFrontmatter = /^---\r?\n[\s\S]*?\r?\n---(?:\r?\n|$)/.test(content);
11222
+ let parsed = null;
11223
+ let parseError = null;
11224
+ if (hasFrontmatter) {
11225
+ try {
11226
+ parsed = (0, gray_matter_1.default)(content);
11227
+ }
11228
+ catch (error) {
11229
+ parseError = error;
11230
+ }
11231
+ }
11232
+ const data = parsed?.data ?? {};
11233
+ const optionalStepsFieldValid = Array.isArray(data.optional_steps);
11234
+ const passedOptionalStepsFieldValid = Array.isArray(data.passed_optional_steps);
11235
+ const optionalSteps = optionalStepsFieldValid ? data.optional_steps : [];
11236
+ const passedOptionalSteps = passedOptionalStepsFieldValid ? data.passed_optional_steps : [];
11237
+ const createdFieldValid = (typeof data.created === 'string' && data.created.trim().length > 0) ||
11238
+ (data.created instanceof Date && !Number.isNaN(data.created.getTime()));
11239
+ const missingRequiredFields = [];
11240
+ if (typeof data.feature !== 'string' || data.feature.trim().length === 0) {
11241
+ missingRequiredFields.push('feature');
11242
+ }
11243
+ if (!createdFieldValid) {
11244
+ missingRequiredFields.push('created');
11245
+ }
11246
+ if (typeof data.status !== 'string' || data.status.trim().length === 0) {
11247
+ missingRequiredFields.push('status');
11248
+ }
11249
+ if (!optionalStepsFieldValid) {
11250
+ missingRequiredFields.push('optional_steps');
11251
+ }
11252
+ if (!passedOptionalStepsFieldValid) {
11253
+ missingRequiredFields.push('passed_optional_steps');
11254
+ }
11255
+ const missing = optionalStepsFieldValid
11256
+ ? activatedSteps.filter(step => !optionalSteps.includes(step))
11257
+ : [...activatedSteps];
11258
+ const checklistItems = parsed?.content.match(/^\s*-\s+\[(?: |x|X)\]\s+.+$/gm) ?? [];
11259
+ const uncheckedItems = parsed?.content.match(/^\s*-\s+\[ \]\s+.+$/gm) ?? [];
11260
+ const checklistStructureValid = checklistItems.length > 0;
11261
+ const checklistComplete = hasFrontmatter &&
11262
+ parseError === null &&
11263
+ missingRequiredFields.length === 0 &&
11264
+ checklistStructureValid &&
11265
+ uncheckedItems.length === 0;
11266
+ let frontmatterMessage = 'verification.md frontmatter parsed successfully';
11267
+ if (!hasFrontmatter) {
11268
+ frontmatterMessage = 'verification.md is missing a valid frontmatter block';
11269
+ }
11270
+ else if (parseError) {
11271
+ frontmatterMessage = `verification.md frontmatter cannot be parsed: ${parseError.message}`;
11272
+ }
11273
+ let requiredFieldsMessage = 'verification.md has all required frontmatter fields';
11274
+ if (!hasFrontmatter || parseError) {
11275
+ requiredFieldsMessage = 'Cannot validate required fields in verification.md because frontmatter is invalid';
11276
+ }
11277
+ else if (missingRequiredFields.length > 0) {
11278
+ requiredFieldsMessage = `Missing or invalid required fields in verification.md: ${missingRequiredFields.join(', ')}`;
11279
+ }
11280
+ let optionalStepsMessage = 'All activated optional steps are present in verification.md';
11281
+ if (!optionalStepsFieldValid) {
11282
+ optionalStepsMessage = 'verification.md frontmatter field optional_steps must be an array';
11283
+ }
11284
+ else if (missing.length > 0) {
11285
+ optionalStepsMessage = `Missing optional steps in verification.md: ${missing.join(', ')}`;
11286
+ }
11287
+ let checklistStatus = 'pass';
11288
+ let checklistMessage = 'verification.md checklist is complete';
11289
+ if (!hasFrontmatter || parseError) {
11290
+ checklistStatus = 'fail';
11291
+ checklistMessage = 'verification.md checklist cannot be validated because frontmatter is invalid';
11292
+ }
11293
+ else if (!checklistStructureValid) {
11294
+ checklistStatus = 'fail';
11295
+ checklistMessage = 'verification.md must contain at least one Markdown checklist item';
11296
+ }
11297
+ else if (uncheckedItems.length > 0) {
11298
+ checklistStatus = 'warn';
11299
+ checklistMessage = 'verification.md still has unchecked items';
11300
+ }
11316
11301
  return {
11317
-
11318
-
11319
-
11320
-
11321
-
11322
-
11323
-
11324
11302
  optionalSteps,
11325
-
11326
-
11327
-
11328
-
11329
-
11330
-
11331
-
11332
11303
  passedOptionalSteps,
11333
-
11334
-
11335
-
11336
-
11337
-
11338
-
11339
-
11340
11304
  checklistComplete,
11341
-
11342
-
11343
-
11344
-
11345
-
11346
-
11347
-
11348
11305
  checks: [
11349
-
11350
-
11351
-
11352
-
11353
-
11354
-
11355
-
11356
11306
  {
11357
-
11358
-
11359
-
11360
-
11361
-
11362
-
11363
-
11307
+ name: 'verification.md.frontmatter',
11308
+ status: hasFrontmatter && parseError === null ? 'pass' : 'fail',
11309
+ message: frontmatterMessage,
11310
+ },
11311
+ {
11312
+ name: 'verification.md.required_fields',
11313
+ status: hasFrontmatter && parseError === null && missingRequiredFields.length === 0 ? 'pass' : 'fail',
11314
+ message: requiredFieldsMessage,
11315
+ },
11316
+ {
11364
11317
  name: 'verification.md.optional_steps',
11365
-
11366
-
11367
-
11368
-
11369
-
11370
-
11371
-
11372
- status: missing.length === 0 ? 'pass' : 'fail',
11373
-
11374
-
11375
-
11376
-
11377
-
11378
-
11379
-
11380
- message: missing.length === 0
11381
-
11382
-
11383
-
11384
-
11385
-
11386
-
11387
-
11388
- ? 'All activated optional steps are present in verification.md'
11389
-
11390
-
11391
-
11392
-
11393
-
11394
-
11395
-
11396
- : `Missing optional steps in verification.md: ${missing.join(', ')}`,
11397
-
11398
-
11399
-
11400
-
11401
-
11402
-
11403
-
11318
+ status: optionalStepsFieldValid && missing.length === 0 ? 'pass' : 'fail',
11319
+ message: optionalStepsMessage,
11404
11320
  },
11405
-
11406
-
11407
-
11408
-
11409
-
11410
-
11411
-
11412
11321
  {
11413
-
11414
-
11415
-
11416
-
11417
-
11418
-
11419
-
11420
11322
  name: 'verification.md.checklist',
11421
-
11422
-
11423
-
11424
-
11425
-
11426
-
11427
-
11428
- status: checklistComplete ? 'pass' : 'warn',
11429
-
11430
-
11431
-
11432
-
11433
-
11434
-
11435
-
11436
- message: checklistComplete
11437
-
11438
-
11439
-
11440
-
11441
-
11442
-
11443
-
11444
- ? 'verification.md checklist is complete'
11445
-
11446
-
11447
-
11448
-
11449
-
11450
-
11451
-
11452
- : 'verification.md still has unchecked items',
11453
-
11454
-
11455
-
11456
-
11457
-
11458
-
11459
-
11323
+ status: checklistStatus,
11324
+ message: checklistMessage,
11460
11325
  },
11461
-
11462
-
11463
-
11464
-
11465
-
11466
-
11467
-
11468
11326
  ],
11469
-
11470
-
11471
-
11472
-
11473
-
11474
-
11475
-
11476
11327
  };
11477
11328
 
11478
11329