update-base-config 0.1.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/README.md ADDED
@@ -0,0 +1,27 @@
1
+ # update-base-config
2
+
3
+ `update-base-config` is a small `npx` CLI that copies shared project-local AI agent skills from this repository into another repository.
4
+
5
+ ## What it syncs
6
+
7
+ The CLI copies each skill from `templates/skills/` into `.agents/skills/`, `.claude/skills/`, `.opencode/skills/`, and `.codex/skills/`.
8
+
9
+ ## Behavior
10
+
11
+ - Shows a summary before changing files
12
+ - Asks for confirmation by default
13
+ - Creates a backup before replacing an existing skill file
14
+
15
+ Backups are stored inside the target project under `.update-base-config-backups/<timestamp>/`.
16
+
17
+ ## Usage
18
+
19
+ ```bash
20
+ npx update-base-config
21
+ npx update-base-config --dry-run
22
+ npx update-base-config --yes
23
+ ```
24
+
25
+ ## Develop the template
26
+
27
+ Edit every shared skill once inside `templates/skills/`. The CLI distributes that master source into each supported local agent directory.
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "update-base-config",
3
+ "version": "0.1.0",
4
+ "description": "Install and update shared AI agent project configuration from a fixed template.",
5
+ "type": "module",
6
+ "bin": {
7
+ "update-base-config": "./src/index.js"
8
+ },
9
+ "files": [
10
+ "src",
11
+ "templates",
12
+ "README.md"
13
+ ],
14
+ "scripts": {
15
+ "test": "node --test"
16
+ },
17
+ "engines": {
18
+ "node": ">=18.17"
19
+ },
20
+ "license": "MIT"
21
+ }
package/src/index.js ADDED
@@ -0,0 +1,153 @@
1
+ #!/usr/bin/env node
2
+
3
+ import path from 'node:path';
4
+ import process from 'node:process';
5
+ import { access } from 'node:fs/promises';
6
+ import { constants } from 'node:fs';
7
+ import readline from 'node:readline/promises';
8
+ import {
9
+ applyOperations,
10
+ buildBackupRoot,
11
+ formatSummary,
12
+ getDefaultSkillsRoot,
13
+ planSync,
14
+ summarizeOperations
15
+ } from './lib.js';
16
+
17
+ async function main() {
18
+ const args = parseArgs(process.argv.slice(2));
19
+
20
+ if (args.help) {
21
+ printHelp();
22
+ return;
23
+ }
24
+
25
+ const targetRoot = path.resolve(args.targetRoot ?? process.cwd());
26
+ const skillsRoot = path.resolve(args.skillsRoot ?? getDefaultSkillsRoot());
27
+
28
+ await ensureReadableDirectory(skillsRoot, 'skills template');
29
+ await ensureReadableDirectory(targetRoot, 'target');
30
+
31
+ const operations = await planSync({ skillsRoot, targetRoot });
32
+ const actionable = operations.filter((operation) => operation.type !== 'skip');
33
+
34
+ console.log(formatSummary(operations, targetRoot));
35
+
36
+ if (actionable.length === 0) {
37
+ console.log('\nNo changes required.');
38
+ return;
39
+ }
40
+
41
+ if (args.dryRun) {
42
+ console.log('\nDry run only. No files were modified.');
43
+ return;
44
+ }
45
+
46
+ if (!args.yes) {
47
+ const confirmed = await askForConfirmation();
48
+ if (!confirmed) {
49
+ console.log('Cancelled.');
50
+ process.exitCode = 1;
51
+ return;
52
+ }
53
+ }
54
+
55
+ const backupRoot = buildBackupRoot(targetRoot);
56
+ const result = await applyOperations({ operations, backupRoot });
57
+ const summary = summarizeOperations(operations);
58
+
59
+ console.log('');
60
+ console.log(`Applied ${summary.create + summary.merge + summary.replace} changes.`);
61
+ if (result.backupCreated) {
62
+ console.log(`Backups saved in ${backupRoot}`);
63
+ }
64
+ }
65
+
66
+ function parseArgs(argv) {
67
+ const args = {
68
+ yes: false,
69
+ dryRun: false,
70
+ help: false,
71
+ targetRoot: null,
72
+ skillsRoot: null
73
+ };
74
+
75
+ for (let index = 0; index < argv.length; index += 1) {
76
+ const token = argv[index];
77
+
78
+ if (token === '--yes' || token === '-y') {
79
+ args.yes = true;
80
+ continue;
81
+ }
82
+
83
+ if (token === '--dry-run') {
84
+ args.dryRun = true;
85
+ continue;
86
+ }
87
+
88
+ if (token === '--help' || token === '-h') {
89
+ args.help = true;
90
+ continue;
91
+ }
92
+
93
+ if (token === '--target') {
94
+ args.targetRoot = argv[index + 1];
95
+ index += 1;
96
+ continue;
97
+ }
98
+
99
+ if (token === '--skills-root') {
100
+ args.skillsRoot = argv[index + 1];
101
+ index += 1;
102
+ continue;
103
+ }
104
+
105
+ throw new Error(`Unknown argument: ${token}`);
106
+ }
107
+
108
+ return args;
109
+ }
110
+
111
+ function printHelp() {
112
+ console.log(`update-base-config
113
+
114
+ Usage:
115
+ npx update-base-config
116
+ npx update-base-config --dry-run
117
+ npx update-base-config --yes
118
+
119
+ Options:
120
+ --yes, -y Apply without asking for confirmation
121
+ --dry-run Show the plan without writing files
122
+ --target <path> Override the target project directory
123
+ --skills-root Override the master skills directory
124
+ --help, -h Show this message
125
+ `);
126
+ }
127
+
128
+ async function ensureReadableDirectory(directoryPath, label) {
129
+ try {
130
+ await access(directoryPath, constants.R_OK);
131
+ } catch {
132
+ throw new Error(`Cannot read ${label} directory: ${directoryPath}`);
133
+ }
134
+ }
135
+
136
+ async function askForConfirmation() {
137
+ const rl = readline.createInterface({
138
+ input: process.stdin,
139
+ output: process.stdout
140
+ });
141
+
142
+ try {
143
+ const answer = await rl.question('\nApply these changes? (y/N) ');
144
+ return answer.trim().toLowerCase() === 'y';
145
+ } finally {
146
+ rl.close();
147
+ }
148
+ }
149
+
150
+ main().catch((error) => {
151
+ console.error(error.message);
152
+ process.exitCode = 1;
153
+ });
package/src/lib.js ADDED
@@ -0,0 +1,144 @@
1
+ import path from 'node:path';
2
+ import { mkdir, readFile, readdir, stat, writeFile, copyFile } from 'node:fs/promises';
3
+
4
+ import { fileURLToPath } from 'node:url';
5
+
6
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
+
8
+ export function getDefaultSkillsRoot() {
9
+ return path.resolve(__dirname, '..', 'templates', 'skills');
10
+ }
11
+
12
+ export async function collectFiles(rootDir) {
13
+ const entries = await readdir(rootDir, { withFileTypes: true });
14
+ const files = [];
15
+
16
+ for (const entry of entries) {
17
+ if (entry.name === '.DS_Store') {
18
+ continue;
19
+ }
20
+
21
+ const absolutePath = path.join(rootDir, entry.name);
22
+
23
+ if (entry.isDirectory()) {
24
+ files.push(...(await collectFiles(absolutePath)));
25
+ continue;
26
+ }
27
+
28
+ if (entry.isFile()) {
29
+ files.push(absolutePath);
30
+ }
31
+ }
32
+
33
+ return files.sort();
34
+ }
35
+
36
+ export async function planSync({ skillsRoot, targetRoot }) {
37
+ const skillFiles = await collectFiles(skillsRoot);
38
+ const operations = [];
39
+
40
+ for (const sourcePath of skillFiles) {
41
+ const skillPath = path.relative(skillsRoot, sourcePath);
42
+
43
+ for (const agentRoot of ['.agents', '.claude', '.opencode', '.codex']) {
44
+ const relativePath = path.join(agentRoot, 'skills', skillPath);
45
+ operations.push(...(await planFile({ sourcePath, relativePath, targetRoot })));
46
+ }
47
+ }
48
+
49
+ return operations;
50
+ }
51
+
52
+ async function planFile({ sourcePath, relativePath, targetRoot }) {
53
+ const targetPath = path.join(targetRoot, relativePath);
54
+ const targetInfo = await readExistingFile(targetPath);
55
+ const sourceContent = await readFile(sourcePath, 'utf8');
56
+
57
+ if (!targetInfo.exists) {
58
+ return [{ type: 'create', relativePath, sourcePath, targetPath, content: sourceContent }];
59
+ }
60
+
61
+ if (targetInfo.content === sourceContent) {
62
+ return [{ type: 'skip', relativePath, sourcePath, targetPath, reason: 'already up to date' }];
63
+ }
64
+
65
+ return [{ type: 'replace', relativePath, sourcePath, targetPath, content: sourceContent }];
66
+ }
67
+
68
+ async function readExistingFile(filePath) {
69
+ try {
70
+ const fileStat = await stat(filePath);
71
+ if (!fileStat.isFile()) {
72
+ return { exists: false, content: null };
73
+ }
74
+
75
+ return {
76
+ exists: true,
77
+ content: await readFile(filePath, 'utf8')
78
+ };
79
+ } catch {
80
+ return { exists: false, content: null };
81
+ }
82
+ }
83
+
84
+ export function summarizeOperations(operations) {
85
+ return operations.reduce(
86
+ (summary, operation) => {
87
+ summary[operation.type] = (summary[operation.type] ?? 0) + 1;
88
+ return summary;
89
+ },
90
+ { create: 0, merge: 0, replace: 0, skip: 0 }
91
+ );
92
+ }
93
+
94
+ export function formatSummary(operations, targetRoot) {
95
+ const summary = summarizeOperations(operations);
96
+ const lines = [
97
+ `Target: ${targetRoot}`,
98
+ `Create: ${summary.create}`,
99
+ `Merge: ${summary.merge}`,
100
+ `Replace with backup: ${summary.replace}`,
101
+ `Skip: ${summary.skip}`,
102
+ '',
103
+ 'Planned changes:'
104
+ ];
105
+
106
+ for (const operation of operations) {
107
+ if (operation.type === 'skip') {
108
+ continue;
109
+ }
110
+
111
+ const detail = operation.type === 'merge' ? ` (${operation.mergeKind})` : '';
112
+ lines.push(`- ${operation.type.toUpperCase()} ${operation.relativePath}${detail}`);
113
+ }
114
+
115
+ return lines.join('\n');
116
+ }
117
+
118
+ export async function applyOperations({ operations, backupRoot }) {
119
+ let backupCreated = false;
120
+
121
+ for (const operation of operations) {
122
+ if (operation.type === 'skip') {
123
+ continue;
124
+ }
125
+
126
+ await mkdir(path.dirname(operation.targetPath), { recursive: true });
127
+
128
+ if (operation.type === 'replace') {
129
+ const backupPath = path.join(backupRoot, operation.relativePath);
130
+ await mkdir(path.dirname(backupPath), { recursive: true });
131
+ await copyFile(operation.targetPath, backupPath);
132
+ backupCreated = true;
133
+ }
134
+
135
+ await writeFile(operation.targetPath, operation.content, 'utf8');
136
+ }
137
+
138
+ return { backupCreated };
139
+ }
140
+
141
+ export function buildBackupRoot(targetRoot, now = new Date()) {
142
+ const timestamp = now.toISOString().replace(/[:.]/g, '-');
143
+ return path.join(targetRoot, '.update-base-config-backups', timestamp);
144
+ }
@@ -0,0 +1,40 @@
1
+ ---
2
+ name: ids
3
+ description: Gestiona y valida IDs en elementos interactivos del proyecto m-personas con formato estándar para analytics y calidad.
4
+ license: MIT
5
+ metadata:
6
+ author: m-personas
7
+ version: "1.0"
8
+ ---
9
+
10
+ # Skill: Gestión de IDs
11
+
12
+ Usa esta skill cuando necesites:
13
+
14
+ - Detectar elementos interactivos sin `id`
15
+ - Corregir IDs con formato inválido
16
+ - Aplicar auto-fix de ESLint para IDs
17
+ - Revisar el estándar `[PREFIX]_landing-[TIPO]-[CONTEXTO]`
18
+
19
+ ## Documentación
20
+
21
+ - Inicio rápido: [docs/SKILL_QUICK_START.md](./docs/SKILL_QUICK_START.md)
22
+ - Guía general: [docs/SKILL_README.md](./docs/SKILL_README.md)
23
+ - Guía técnica: [docs/SKILL_ID_MANAGEMENT.md](./docs/SKILL_ID_MANAGEMENT.md)
24
+ - Resumen visual: [docs/SKILL_INICIO.txt](./docs/SKILL_INICIO.txt)
25
+
26
+ ## Comandos útiles
27
+
28
+ - `npx eslint --fix src/`
29
+ - `node scripts/manageIds.js --analyze src/pages`
30
+ - `node scripts/manageIds.js --report`
31
+
32
+ ## Notas de proyecto
33
+
34
+ - Prefijo esperado por defecto: `ppp_` (Banco Chile)
35
+ - En Banco Edwards: `pbec_`
36
+ - La validación es warning en desarrollo y error en producción
37
+
38
+ ## Cómo resolverlo
39
+
40
+ Usa ids con el formato `[PREFIX]_[SECCION]-[TIPO]-[CONTEXTO]`, donde `[SECCION]` puede ser `landing`, `header` o `footer`; `link` para `<a>` y `btn` para `<button>`.