@pasosdejesus/mpdj 0.1.0 → 0.2.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.
- package/SKILL.md +8 -0
- package/dist/SKILL.md +8 -0
- package/dist/index.js +4 -5
- package/dist/tasks/credentials.js +18 -1
- package/dist/tasks/req.d.ts +1 -1
- package/dist/tasks/req.js +52 -20
- package/package.json +2 -2
package/SKILL.md
CHANGED
|
@@ -159,6 +159,14 @@ If you have direct terminal and filesystem access (Crush, Cursor, etc.), **do no
|
|
|
159
159
|
./bin/m db:check # Verifies expected DB schemas (WBA, credentials) against actual DB
|
|
160
160
|
```
|
|
161
161
|
|
|
162
|
+
### DB Environment Variables
|
|
163
|
+
|
|
164
|
+
| Variable | Default | Purpose |
|
|
165
|
+
|----------|---------|---------|
|
|
166
|
+
| `PG_TIMEOUT` | `30` | Migration timeout in seconds |
|
|
167
|
+
| `M_POST_MIGRATE` | — | Command to run after `db:migrate`/`db:rollback` |
|
|
168
|
+
```
|
|
169
|
+
|
|
162
170
|
### Engine Commands
|
|
163
171
|
|
|
164
172
|
```bash
|
package/dist/SKILL.md
CHANGED
|
@@ -159,6 +159,14 @@ If you have direct terminal and filesystem access (Crush, Cursor, etc.), **do no
|
|
|
159
159
|
./bin/m db:check # Verifies expected DB schemas (WBA, credentials) against actual DB
|
|
160
160
|
```
|
|
161
161
|
|
|
162
|
+
### DB Environment Variables
|
|
163
|
+
|
|
164
|
+
| Variable | Default | Purpose |
|
|
165
|
+
|----------|---------|---------|
|
|
166
|
+
| `PG_TIMEOUT` | `30` | Migration timeout in seconds |
|
|
167
|
+
| `M_POST_MIGRATE` | — | Command to run after `db:migrate`/`db:rollback` |
|
|
168
|
+
```
|
|
169
|
+
|
|
162
170
|
### Engine Commands
|
|
163
171
|
|
|
164
172
|
```bash
|
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(
|
|
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
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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,
|
|
@@ -26,6 +26,7 @@ import { problem } from '@pasosdejesus/m/dist/util/colors.js';
|
|
|
26
26
|
import { createTranslator } from '@pasosdejesus/m/dist/i18n/createTranslator.js';
|
|
27
27
|
import { detectLocale } from '@pasosdejesus/m/dist/util/locale.js';
|
|
28
28
|
import commonCli from '@pasosdejesus/m/dist/i18n/common-cli.js';
|
|
29
|
+
import { runHooks } from '@pasosdejesus/m/dist/plugin.js';
|
|
29
30
|
const require = createRequire(import.meta.url);
|
|
30
31
|
const locale = detectLocale();
|
|
31
32
|
const ct = createTranslator(locale, {
|
|
@@ -335,8 +336,10 @@ async function registerType(args) {
|
|
|
335
336
|
console.warn(ct('cred.image_failed', String(tokenId), err.message));
|
|
336
337
|
}
|
|
337
338
|
}
|
|
339
|
+
// Save to credential_metadata cache
|
|
340
|
+
let db = null;
|
|
338
341
|
try {
|
|
339
|
-
|
|
342
|
+
db = await newDb();
|
|
340
343
|
await db
|
|
341
344
|
.insertInto('credential_metadata')
|
|
342
345
|
.values({
|
|
@@ -359,6 +362,10 @@ async function registerType(args) {
|
|
|
359
362
|
catch (err) {
|
|
360
363
|
console.warn(ct('cred.cache_failed', err.message));
|
|
361
364
|
}
|
|
365
|
+
// Notify extensions (learn.tg#200: course_id handling)
|
|
366
|
+
if (courseId > 0 && db) {
|
|
367
|
+
await runHooks('credentials:post-register-type', { tokenId, courseId, db });
|
|
368
|
+
}
|
|
362
369
|
console.log(ct('cred.register_success', String(tokenId), site, type, display));
|
|
363
370
|
}
|
|
364
371
|
async function recomposeImage(args) {
|
|
@@ -475,6 +482,16 @@ async function syncCache(network) {
|
|
|
475
482
|
.execute();
|
|
476
483
|
count++;
|
|
477
484
|
console.log(ct('cred.sync_row', String(i), String(name)));
|
|
485
|
+
// Notify extensions (learn.tg#200: course_id handling)
|
|
486
|
+
let courseId = 0;
|
|
487
|
+
try {
|
|
488
|
+
courseId = Number(await pc.readContract({
|
|
489
|
+
address: addr, abi: credentialsAbi,
|
|
490
|
+
functionName: 'tokenIdToCourseId', args: [BigInt(i)],
|
|
491
|
+
}));
|
|
492
|
+
}
|
|
493
|
+
catch { }
|
|
494
|
+
await runHooks('credentials:post-sync-cache', { tokenId: i, courseId, db });
|
|
478
495
|
}
|
|
479
496
|
catch { /* skip */ }
|
|
480
497
|
}
|
package/dist/tasks/req.d.ts
CHANGED
|
@@ -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(
|
|
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*([
|
|
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(/^[
|
|
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*)?[
|
|
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(/^[
|
|
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
|
|
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
|
|
574
|
-
const
|
|
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(/^[
|
|
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(/^[
|
|
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(/^[
|
|
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(/^[
|
|
792
|
-
const issueTypeMatch = (issue.title || '').match(/^([
|
|
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*[
|
|
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
|
-
|
|
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
|
|
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(`([
|
|
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(
|
|
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
|
-
|
|
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,
|
|
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.
|
|
3
|
+
"version": "0.2.0",
|
|
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.
|
|
58
|
+
"@pasosdejesus/m": "0.17.0"
|
|
59
59
|
},
|
|
60
60
|
"peerDependencies": {
|
|
61
61
|
"vitest": "^2.1.9"
|