@pasosdejesus/mpdj 0.1.0 → 0.1.1

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/dist/index.js CHANGED
@@ -80,7 +80,7 @@ const commands = {
80
80
  handler: (n) => {
81
81
  if (!n)
82
82
  throw new Error('req:sync-to-repo <N> [N2 ...]');
83
- return runReqSyncToRepo([n, ...process.argv.slice(3)].join(' '));
83
+ return runReqSyncToRepo([n, ...process.argv.slice(4)].join(' '));
84
84
  },
85
85
  description: 'Updates remote issue from local REQ',
86
86
  argRequired: true,
@@ -101,10 +101,9 @@ const commands = {
101
101
  },
102
102
  'req:close': {
103
103
  handler: (n) => {
104
- const num = parseInt(n || '', 10);
105
- if (isNaN(num))
106
- throw new Error('req:close <N>');
107
- return runReqClose(num);
104
+ if (!n)
105
+ throw new Error('req:close <N> [N2 N3 ...]');
106
+ return runReqClose([n, ...process.argv.slice(4)].join(' '));
108
107
  },
109
108
  description: 'Closes a requirement',
110
109
  argRequired: true,
@@ -6,4 +6,4 @@ export declare function runReqCompare(number?: number): Promise<void>;
6
6
  export declare function runReqSyncFromRepo(arg?: string): Promise<void>;
7
7
  export declare function runReqSyncToRepo(arg?: string): Promise<void>;
8
8
  export declare function runReqCreate(title?: string, typeArg?: string): Promise<void>;
9
- export declare function runReqClose(number: number): Promise<void>;
9
+ export declare function runReqClose(arg?: string): Promise<void>;
package/dist/tasks/req.js CHANGED
@@ -67,10 +67,12 @@ function parseReqFile(content, number) {
67
67
  let title = '';
68
68
  let issueType = 'R';
69
69
  const firstLine = lines[0] || '';
70
- const headerMatch = firstLine.match(/^#\s*([RFIP])-#(\d+):?\s*(.*)/);
70
+ const headerMatch = firstLine.match(/^#\s*([RFIPE])-#(\d+):?\s*(.*)/);
71
71
  if (headerMatch) {
72
72
  issueType = headerMatch[1];
73
73
  title = headerMatch[3]?.trim().replace(/`/g, '') || '';
74
+ // Strip any embedded old prefix (e.g., "I-#1 Titulo" → "Titulo")
75
+ title = title.replace(/^[RFIPE]-#\d+:\s*/, '');
74
76
  }
75
77
  // ── Fallback: old YAML frontmatter ───────────────
76
78
  if (!title && lines[0]?.trim() === '---') {
@@ -80,7 +82,7 @@ function parseReqFile(content, number) {
80
82
  const titleMatch = lines[i].match(/^title:\s*"?(.+?)"?\s*$/);
81
83
  if (titleMatch) {
82
84
  title = titleMatch[1]
83
- .replace(/^[RFIP]-#\d+:\s*/, '')
85
+ .replace(/^[RFIPE]-#\d+:\s*/, '')
84
86
  .replace(/`/g, '')
85
87
  .trim();
86
88
  }
@@ -89,7 +91,7 @@ function parseReqFile(content, number) {
89
91
  }
90
92
  // ── Fallback: old format first-line ──────────────
91
93
  if (!title) {
92
- const oldMatch = firstLine.match(/^(?:#\s*)?[RFIP]-#\d+\s*(.*)/);
94
+ const oldMatch = firstLine.match(/^(?:#\s*)?[RFIPE]-#\d+\s*(.*)/);
93
95
  if (oldMatch) {
94
96
  title = (oldMatch[1] || '').trim().replace(/`/g, '');
95
97
  }
@@ -99,7 +101,7 @@ function parseReqFile(content, number) {
99
101
  for (const line of lines) {
100
102
  const m = line.match(/^title:\s*"?(.+?)"?\s*$/);
101
103
  if (m) {
102
- title = m[1].replace(/^[RFIP]-#\d+:\s*/, '').replace(/`/g, '').trim();
104
+ title = m[1].replace(/^[RFIPE]-#\d+:\s*/, '').replace(/`/g, '').trim();
103
105
  break;
104
106
  }
105
107
  }
@@ -526,9 +528,10 @@ async function runCompareAll(config, token) {
526
528
  const reqDir = await findReqDir();
527
529
  const localReqs = await loadAllReqs(reqDir);
528
530
  const localNums = new Set(localReqs.map(r => r.number));
529
- // Pre-load local mtimes and content fingerprints
531
+ // Pre-load local mtimes, content fingerprints, and title format
530
532
  const localMtimeMap = new Map();
531
533
  const localFingerprintMap = new Map();
534
+ const localHasPrefix = new Map();
532
535
  for (const r of localReqs) {
533
536
  try {
534
537
  const s = await stat(join(reqDir, `${r.number}.md`));
@@ -536,6 +539,8 @@ async function runCompareAll(config, token) {
536
539
  const content = await readFile(join(reqDir, `${r.number}.md`), 'utf-8');
537
540
  const normalized = content.trim().replace(/\r/g, '').replace(/\n{3,}/g, '\n\n');
538
541
  localFingerprintMap.set(r.number, `${normalized.length}:${normalized.slice(0, 200)}`);
542
+ // Check if local REQ has proper R-#N: / F-#N: / I-#N: / P-#N: title format
543
+ localHasPrefix.set(r.number, /^#\s*[RFIPE]-#\d+:/.test(content));
539
544
  }
540
545
  catch {
541
546
  // file inaccessible
@@ -570,8 +575,9 @@ async function runCompareAll(config, token) {
570
575
  const shown = new Set();
571
576
  for (const issue of issues) {
572
577
  const hasLocal = localNums.has(issue.iid);
573
- const typeMatch = issue.title.match(/^([RFIP])-#[?\d]/);
574
- const issueType = typeMatch ? typeMatch[1] : 'R';
578
+ const localReq = localReqs.find(r => r.number === issue.iid);
579
+ const typeMatch = issue.title.match(/^([RFIPE])-#[?\d]/);
580
+ const issueType = (hasLocal && localReq) ? localReq.type : (typeMatch ? typeMatch[1] : 'R');
575
581
  shown.add(issue.iid);
576
582
  let sync = '—';
577
583
  if (hasLocal && issue.updated_at) {
@@ -597,10 +603,16 @@ async function runCompareAll(config, token) {
597
603
  else {
598
604
  sync = 'loc+';
599
605
  }
606
+ // Title format check: if local has R-#N: prefix and remote doesn't, local wins
607
+ if (sync === 'sync' && localHasPrefix.get(issue.iid)) {
608
+ const remoteHasPrefix = /^[RFIPE]-#[?\d]+:/.test(issue.title);
609
+ if (!remoteHasPrefix)
610
+ sync = 'loc+';
611
+ }
600
612
  }
601
613
  }
602
614
  }
603
- const displayTitle = issue.title.replace(/^[RFIP]-#[?\d]+:?\s*/, '').replace(/`/g, '').trim();
615
+ const displayTitle = issue.title.replace(/^[RFIPE]-#[?\d]+:?\s*/, '').replace(/`/g, '').trim();
604
616
  console.log(` ${String(issue.iid).padEnd(4)} │ ${issueType.padEnd(4)} │ ${displayTitle.slice(0, titleW).padEnd(titleW)} │ ${hasLocal ? ' ✓' : ' ✗'} │ ${shortState(issue.state).padEnd(stateW)} │ ${sync}`);
605
617
  }
606
618
  // Show local-only REQs (not in remote open issues)
@@ -614,7 +626,7 @@ async function runCompareAll(config, token) {
614
626
  try {
615
627
  const issue = await fetchRemoteIssue(config, r.number, token);
616
628
  remoteState = issue.state || 'unknown';
617
- remoteTitle = (issue.title || '').replace(/^[RFIP]-#[?\d]+:?\s*/, '').trim();
629
+ remoteTitle = (issue.title || '').replace(/^[RFIPE]-#[?\d]+:?\s*/, '').trim();
618
630
  remoteUpdatedAt = issue.updated_at || issue.updatedAt || null;
619
631
  shown.add(r.number);
620
632
  if (remoteUpdatedAt) {
@@ -646,6 +658,12 @@ async function runCompareAll(config, token) {
646
658
  catch {
647
659
  // Issue doesn't exist remotely
648
660
  }
661
+ // Title format check: if local has prefix and remote doesn't, local wins
662
+ if (sync === 'sync' && localHasPrefix.get(r.number)) {
663
+ const remoteHasPrefix = /^[RFIPE]-#[?\d]+:/.test(remoteTitle || '');
664
+ if (!remoteHasPrefix && remoteState !== '—')
665
+ sync = 'loc+';
666
+ }
649
667
  const title = remoteTitle || r.title;
650
668
  console.log(` ${String(r.number).padEnd(4)} │ ${r.type.padEnd(4)} │ ${title.slice(0, titleW).padEnd(titleW)} │ ✓ │ ${shortState(remoteState).padEnd(stateW)} │ ${sync}`);
651
669
  }
@@ -693,7 +711,7 @@ export async function runReqCompare(number) {
693
711
  try {
694
712
  const issue = await fetchRemoteIssue(config, number, token);
695
713
  const remoteDesc = issue.description || issue.body || '';
696
- const remoteTitle = (issue.title || '').replace(/^[RFIP]-#\d+:\s*/, '').trim();
714
+ const remoteTitle = (issue.title || '').replace(/^[RFIPE]-#\d+:\s*/, '').trim();
697
715
  const remoteState = issue.state || 'unknown';
698
716
  const remoteUpdatedAt = issue.updated_at || issue.updatedAt || null;
699
717
  // ── Header info ───────────────────────────────
@@ -788,14 +806,14 @@ async function syncFromRepoOne(number) {
788
806
  try {
789
807
  const issue = await fetchRemoteIssue(config, number, token);
790
808
  const rawBody = issue.description || issue.body || '';
791
- const issueTitle = (issue.title || '').replace(/^[RFIP]-#\d+:\s*/, '').replace(/`/g, '').trim();
792
- const issueTypeMatch = (issue.title || '').match(/^([RFIP])-#\d+:/);
809
+ const issueTitle = (issue.title || '').replace(/^[RFIPE]-#\d+:\s*/, '').replace(/`/g, '').trim();
810
+ const issueTypeMatch = (issue.title || '').match(/^([RFIPE])-#\d+:/);
793
811
  const issueType = issueTypeMatch ? issueTypeMatch[1] : 'R';
794
812
  const status = issue.state === 'closed' ? 'Cerrado' : 'Pendiente';
795
813
  const createdAt = (issue.created_at || issue.createdAt || new Date().toISOString()).slice(0, 10);
796
814
  // Build content: use raw body if it already has our header, otherwise wrap
797
815
  let content;
798
- if (rawBody.trimStart().match(/^(?:---|#\s*[RFIP]-#)/)) {
816
+ if (rawBody.trimStart().match(/^(?:---|#\s*[RFIPE]-#)/)) {
799
817
  content = rawBody;
800
818
  }
801
819
  else {
@@ -940,21 +958,22 @@ async function syncToRepoOne(number) {
940
958
  // Try to update existing issue
941
959
  try {
942
960
  const meta = parseReqFile(localContent, number);
943
- await updateRemoteIssue(config, number, meta.title, localContent, token);
961
+ const fullTitle = `${meta.type}-#${number}: ${meta.title}`;
962
+ await updateRemoteIssue(config, number, fullTitle, localContent, token);
944
963
  success(`✅ Issue remoto #${number} actualizado desde REQ/${number}.md.`);
945
964
  }
946
965
  catch (updateErr) {
947
966
  if (updateErr.message?.includes('404')) {
948
967
  // Issue doesn't exist — create it
949
968
  const meta = parseReqFile(localContent, number);
950
- const newIssue = await createRemoteIssue(config, meta.title, localContent, token);
969
+ const newIssue = await createRemoteIssue(config, `${meta.type}-#?: ${meta.title}`, localContent, token);
951
970
  const remoteIid = newIssue.iid || newIssue.number;
952
971
  info(`✅ Issue creado en remoto como #${remoteIid}.`);
953
972
  if (remoteIid && remoteIid !== number) {
954
973
  // Rename local file to match remote IID
955
974
  const newPath = join(reqDir, `${remoteIid}.md`);
956
975
  const { rename } = await import('node:fs/promises');
957
- let newContent = localContent.replace(new RegExp(`([RFIP])-#${number}\\b`, 'g'), `$1-#${remoteIid}`);
976
+ let newContent = localContent.replace(new RegExp(`([RFIPE])-#${number}\\b`, 'g'), `$1-#${remoteIid}`);
958
977
  await writeFile(newPath, newContent);
959
978
  const { unlink } = await import('node:fs/promises');
960
979
  await unlink(filePath);
@@ -1127,7 +1146,19 @@ async function closeRemoteIssue(config, iid, title, token) {
1127
1146
  }
1128
1147
  throw new Error('Configuración de repositorio incompleta');
1129
1148
  }
1130
- export async function runReqClose(number) {
1149
+ export async function runReqClose(arg) {
1150
+ const numbers = parseNumberList(arg);
1151
+ if (numbers.length === 0) {
1152
+ problem('Uso: req:close <N> [N2 N3 ...]');
1153
+ return;
1154
+ }
1155
+ for (const number of numbers) {
1156
+ info(`Cerrando #${number}...`);
1157
+ await closeOne(number);
1158
+ }
1159
+ success(`✅ ${numbers.length} requerimiento(s) cerrado(s).`);
1160
+ }
1161
+ async function closeOne(number) {
1131
1162
  const reqDir = await findReqDir();
1132
1163
  const filePath = join(reqDir, `${number}.md`);
1133
1164
  if (!existsSync(filePath)) {
@@ -1148,10 +1179,11 @@ export async function runReqClose(number) {
1148
1179
  // 1. Sync local → remote
1149
1180
  const localContent = await readFile(filePath, 'utf-8');
1150
1181
  const meta = parseReqFile(localContent, number);
1151
- await updateRemoteIssue(config, number, meta.title, localContent, token);
1182
+ const fullTitle = `${meta.type}-#${number}: ${meta.title}`;
1183
+ await updateRemoteIssue(config, number, fullTitle, localContent, token);
1152
1184
  info('✓ Sincronizado local → remoto.');
1153
1185
  // 2. Close remote issue
1154
- await closeRemoteIssue(config, number, meta.title, token);
1186
+ await closeRemoteIssue(config, number, fullTitle, token);
1155
1187
  info('✓ Issue cerrado en el repositorio remoto.');
1156
1188
  // 3. Delete local REQ
1157
1189
  const { unlink } = await import('node:fs/promises');
@@ -1159,6 +1191,6 @@ export async function runReqClose(number) {
1159
1191
  success(`✅ REQ/${number}.md cerrado y eliminado.`);
1160
1192
  }
1161
1193
  catch (err) {
1162
- problem(`Error al cerrar: ${err.message}`);
1194
+ problem(`Error al cerrar #${number}: ${err.message}`);
1163
1195
  }
1164
1196
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pasosdejesus/mpdj",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Extensión Pasos de Jesús para m CLI — marco cristiano, credenciales, requerimientos",
5
5
  "author": "",
6
6
  "license": "ISC",
@@ -55,7 +55,7 @@
55
55
  "REQ"
56
56
  ],
57
57
  "dependencies": {
58
- "@pasosdejesus/m": "0.15.0"
58
+ "@pasosdejesus/m": "0.16.1"
59
59
  },
60
60
  "peerDependencies": {
61
61
  "vitest": "^2.1.9"