sdd-gl 0.6.1 → 0.6.2

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.es.md CHANGED
@@ -3,7 +3,7 @@
3
3
  ![SDD-GL Portada](assets/sdd-gl-cover.png)
4
4
 
5
5
  [![npm](https://img.shields.io/npm/v/sdd-gl)](https://www.npmjs.com/package/sdd-gl)
6
- [![Version](https://img.shields.io/badge/version-0.6.1-blue)](CHANGELOG.md)
6
+ [![Version](https://img.shields.io/badge/version-0.6.2-blue)](CHANGELOG.md)
7
7
  [![License](https://img.shields.io/badge/license-MIT-green)](LICENSE)
8
8
  [![MCP Protocol](https://img.shields.io/badge/MCP-Protocol%20Ready-purple)](mcp/sdd-gl-mcp-spec.md)
9
9
  [![Claude Code](https://img.shields.io/badge/Claude%20Code-plugin-orange)](https://docs.claude.ai/code)
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  ![SDD-GL Cover](assets/sdd-gl-cover.png)
4
4
 
5
5
  [![npm](https://img.shields.io/npm/v/sdd-gl)](https://www.npmjs.com/package/sdd-gl)
6
- [![Version](https://img.shields.io/badge/version-0.6.1-blue)](CHANGELOG.md)
6
+ [![Version](https://img.shields.io/badge/version-0.6.2-blue)](CHANGELOG.md)
7
7
  [![License](https://img.shields.io/badge/license-MIT-green)](LICENSE)
8
8
  [![MCP Protocol](https://img.shields.io/badge/MCP-Protocol%20Ready-purple)](mcp/sdd-gl-mcp-spec.md)
9
9
  [![Claude Code](https://img.shields.io/badge/Claude%20Code-plugin-orange)](https://docs.claude.ai/code)
package/bin/sdd.js CHANGED
@@ -32,7 +32,7 @@ const ROOT = rootFlag ? path.resolve(rootFlag) : process.cwd();
32
32
 
33
33
  process.env.SDD_PROJECT_ROOT = ROOT;
34
34
 
35
- const { handleToolCall, TOOLS } = require('../mcp/server.js');
35
+ const { handleToolCall, TOOLS, isContractText, looksLikeContract } = require('../mcp/server.js');
36
36
 
37
37
  // CLI usage is real usage of the methodology: it is counted and attributed to the
38
38
  // contract type it serves (feature/fix) when a contract is involved.
@@ -588,17 +588,28 @@ function commandDoctor() {
588
588
 
589
589
  const unreadable = [];
590
590
  const stalled = [];
591
+ const ignored = [];
592
+ let contracts = 0;
591
593
 
592
594
  if (fs.existsSync(contractsDir)) {
593
595
  for (const name of fs.readdirSync(contractsDir).filter(entry => entry.toLowerCase().endsWith('.md'))) {
594
596
  const text = fs.readFileSync(path.join(contractsDir, name), 'utf8');
595
- if (!/^#\s*ID:/m.test(text) || !/^#\s*Status:/m.test(text)) unreadable.push(name);
597
+
598
+ // `contracts/` may also hold a README or notes: only files with a contract
599
+ // header are counted, and only a broken contract header is a failure.
600
+ if (!isContractText(text)) {
601
+ if (looksLikeContract(text)) unreadable.push(name);
602
+ else ignored.push(name);
603
+ continue;
604
+ }
605
+
606
+ contracts += 1;
596
607
  if (/^\s*#.*\|\s*⏳\s*$/m.test(text)) stalled.push(name);
597
608
  }
598
609
  }
599
610
 
600
611
  if (unreadable.length > 0) add('contratos', 'FAIL', `encabezado ilegible: ${unreadable.join(', ')}`);
601
- else add('contratos', 'OK', 'todos los encabezados son legibles');
612
+ else add('contratos', 'OK', `${contracts} contrato(s) legibles${ignored.length ? ` · ignorados: ${ignored.join(', ')}` : ''}`);
602
613
 
603
614
  if (stalled.length > 0) add('en curso', 'WARN', `ítems en ⏳ (reanudar): ${stalled.join(', ')}`);
604
615
  else add('en curso', 'OK', 'sin ítems interrumpidos');
@@ -1320,6 +1331,7 @@ function commandReview() {
1320
1331
 
1321
1332
  for (const file of fs.readdirSync(contractsDir).filter(name => name.toLowerCase().endsWith('.md'))) {
1322
1333
  const text = fs.readFileSync(path.join(contractsDir, file), 'utf8');
1334
+ if (!isContractText(text)) continue;
1323
1335
  const id = (text.match(/^#\s*ID:\s*(\S+)\s*$/m) || [])[1] || file.replace(/\.md$/, '');
1324
1336
  const status = (text.match(/^#\s*Status:\s*(\w+)\s*$/m) || [])[1] || 'UNKNOWN';
1325
1337
  const criteria = readCriteria(text);
package/mcp/server.js CHANGED
@@ -327,12 +327,30 @@ function inferContractType(contractId, text, fileName) {
327
327
  return 'feature';
328
328
  }
329
329
 
330
+ // A contract is recognized by its header, not by its extension: `contracts/` may
331
+ // also hold a README or free-form notes that must be ignored.
332
+ function isContractText(text) {
333
+ return /^#\s*ID:/m.test(text) && /^#\s*Status:/m.test(text);
334
+ }
335
+
336
+ // Files that *look* like contracts but cannot be read as one are real errors.
337
+ function looksLikeContract(text) {
338
+ return /^#\s*CONTRACT:/m.test(text);
339
+ }
340
+
330
341
  function listContractFiles() {
331
342
  if (!fs.existsSync(CONTRACTS_DIR)) return [];
332
343
  return fs
333
344
  .readdirSync(CONTRACTS_DIR, { withFileTypes: true })
334
345
  .filter(entry => entry.isFile() && entry.name.toLowerCase().endsWith('.md'))
335
- .map(entry => entry.name);
346
+ .map(entry => entry.name)
347
+ .filter(name => {
348
+ try {
349
+ return isContractText(fs.readFileSync(path.join(CONTRACTS_DIR, name), 'utf8'));
350
+ } catch (error) {
351
+ return false;
352
+ }
353
+ });
336
354
  }
337
355
 
338
356
  // Finds a contract by ID regardless of the file naming convention
@@ -1483,7 +1501,7 @@ ${evidence}
1483
1501
 
1484
1502
  case 'sdd_get_status': {
1485
1503
  if (!fs.existsSync(CONTRACTS_DIR)) return { total_contracts: 0, contracts: [] };
1486
- const files = fs.readdirSync(CONTRACTS_DIR).filter(f => f.endsWith('.md'));
1504
+ const files = listContractFiles();
1487
1505
 
1488
1506
  const summary = files.map(file => {
1489
1507
  const content = fs.readFileSync(path.join(CONTRACTS_DIR, file), 'utf8');
@@ -1517,7 +1535,7 @@ ${evidence}
1517
1535
  const response = { ...metrics, source };
1518
1536
  if (warning) response.warning = warning;
1519
1537
  if (fs.existsSync(CONTRACTS_DIR)) {
1520
- response.total_contracts = fs.readdirSync(CONTRACTS_DIR).filter(f => f.endsWith('.md')).length;
1538
+ response.total_contracts = listContractFiles().length;
1521
1539
  } else {
1522
1540
  response.total_contracts = 0;
1523
1541
  }
@@ -1527,7 +1545,7 @@ ${evidence}
1527
1545
  case 'sdd_audit_traceability': {
1528
1546
  // BR-005: real coverage. Nothing here is a fixed value.
1529
1547
  const contractFiles = fs.existsSync(CONTRACTS_DIR)
1530
- ? fs.readdirSync(CONTRACTS_DIR).filter(file => file.endsWith('.md'))
1548
+ ? listContractFiles()
1531
1549
  : [];
1532
1550
 
1533
1551
  const specs = [];
@@ -1665,7 +1683,7 @@ function main() {
1665
1683
  });
1666
1684
  }
1667
1685
 
1668
- module.exports = { TOOLS, handleToolCall, computeNextContractId, reconcileContract, computeCriteriaHashes };
1686
+ module.exports = { TOOLS, handleToolCall, computeNextContractId, reconcileContract, computeCriteriaHashes, isContractText, looksLikeContract };
1669
1687
 
1670
1688
  if (require.main === module) {
1671
1689
  main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sdd-gl",
3
- "version": "0.6.1",
3
+ "version": "0.6.2",
4
4
  "description": "Model Context Protocol (MCP) Server and CLI for Spec-Driven Development (SDD-GL)",
5
5
  "main": "mcp/server.js",
6
6
  "bin": {
@@ -35,7 +35,7 @@
35
35
  "dashboard": "node mcp/dashboard-gen.js",
36
36
  "dashboard:public": "node mcp/dashboard-gen.js --public",
37
37
  "fixture": "node test-sandbox/fixtures/demo-project.js",
38
- "test": "node test-sandbox/id-generation.test.js && node test-sandbox/step-loop.test.js && node test-sandbox/mcp-core.test.js && node test-sandbox/neutral-format.test.js && node test-sandbox/atomicity.test.js && node test-sandbox/metrics.test.js && node test-sandbox/usage.test.js && node test-sandbox/fix-0001-docs.test.js && node test-sandbox/ci-workflow.test.js && node test-sandbox/interop.test.js && node test-sandbox/cli.test.js && node test-sandbox/fixture-project.test.js && node test-sandbox/split.test.js && node test-sandbox/reconcile.test.js && node test-sandbox/review.test.js && node test-sandbox/publish.test.js && node test-sandbox/multilayer-verifier.test.js && node test-sandbox/escrow.test.js && node test-sandbox/mcp-client-e2e.js",
38
+ "test": "node test-sandbox/id-generation.test.js && node test-sandbox/step-loop.test.js && node test-sandbox/mcp-core.test.js && node test-sandbox/neutral-format.test.js && node test-sandbox/atomicity.test.js && node test-sandbox/metrics.test.js && node test-sandbox/usage.test.js && node test-sandbox/fix-0001-docs.test.js && node test-sandbox/ci-workflow.test.js && node test-sandbox/interop.test.js && node test-sandbox/cli.test.js && node test-sandbox/fixture-project.test.js && node test-sandbox/split.test.js && node test-sandbox/reconcile.test.js && node test-sandbox/review.test.js && node test-sandbox/publish.test.js && node test-sandbox/doctor.test.js && node test-sandbox/orchestrator-docs.test.js && node test-sandbox/multilayer-verifier.test.js && node test-sandbox/escrow.test.js && node test-sandbox/mcp-client-e2e.js",
39
39
  "prepublishOnly": "npm test && node scripts/release.js --check",
40
40
  "release:check": "node scripts/release.js --check",
41
41
  "release:prepare": "node scripts/release.js"