gemstack-ai 1.4.0 → 2.0.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.
Files changed (59) hide show
  1. package/.agents/rules/03-gemstack-security.md +2 -2
  2. package/.gemstack/state.json +10 -11
  3. package/CHANGELOG.md +33 -0
  4. package/CONTRIBUTING.md +1 -1
  5. package/README.md +47 -13
  6. package/RELEASE_NOTES.md +40 -1
  7. package/handoff.md +33 -19
  8. package/package.json +4 -3
  9. package/scripts/ci/check-package-contents.js +1 -1
  10. package/scripts/ci/check-secrets.js +84 -0
  11. package/specs/011-gemstack-2.0-hardening/.gemstack.json +5 -0
  12. package/specs/011-gemstack-2.0-hardening/closure.json +58 -0
  13. package/specs/011-gemstack-2.0-hardening/plan.md +210 -0
  14. package/specs/011-gemstack-2.0-hardening/spec.md +277 -0
  15. package/specs/011-gemstack-2.0-hardening/tasks.md +59 -0
  16. package/specs/012-gemstack-2.0-honest-evidence/.gemstack.json +5 -0
  17. package/specs/012-gemstack-2.0-honest-evidence/closure.json +58 -0
  18. package/specs/012-gemstack-2.0-honest-evidence/plan.md +202 -0
  19. package/specs/012-gemstack-2.0-honest-evidence/spec.md +222 -0
  20. package/specs/012-gemstack-2.0-honest-evidence/tasks.md +99 -0
  21. package/specs/013-gemstack-2.0-adaptable-sdd/.gemstack.json +9 -0
  22. package/specs/013-gemstack-2.0-adaptable-sdd/closure.json +58 -0
  23. package/specs/013-gemstack-2.0-adaptable-sdd/context-capsule.json +227 -0
  24. package/specs/013-gemstack-2.0-adaptable-sdd/plan.md +179 -0
  25. package/specs/013-gemstack-2.0-adaptable-sdd/spec.md +212 -0
  26. package/specs/013-gemstack-2.0-adaptable-sdd/tasks.md +90 -0
  27. package/specs/014-gemstack-2.0-context-memory/.gemstack.json +9 -0
  28. package/specs/014-gemstack-2.0-context-memory/closure.json +58 -0
  29. package/specs/014-gemstack-2.0-context-memory/plan.md +161 -0
  30. package/specs/014-gemstack-2.0-context-memory/spec.md +163 -0
  31. package/specs/014-gemstack-2.0-context-memory/tasks.md +79 -0
  32. package/src/cli.js +3 -0
  33. package/src/commands/doctor.js +18 -0
  34. package/src/commands/hooks.js +98 -14
  35. package/src/commands/init.js +1 -1
  36. package/src/commands/install.js +174 -49
  37. package/src/commands/spec.js +105 -0
  38. package/src/commands/update.js +1 -1
  39. package/src/commands/verify.js +10 -0
  40. package/src/lib/backup.js +3 -3
  41. package/src/lib/context-fatigue.js +165 -0
  42. package/src/lib/contract-amendments.js +109 -0
  43. package/src/lib/dependency-audit.js +202 -0
  44. package/src/lib/filesystem-safe.js +85 -15
  45. package/src/lib/memory-audit.js +121 -0
  46. package/src/lib/provider-boundary.js +5 -1
  47. package/src/lib/provider-registry.js +6 -4
  48. package/src/lib/safety-gates.js +176 -8
  49. package/src/lib/sdd-rigor.js +181 -0
  50. package/src/lib/spec-delta.js +194 -0
  51. package/src/lib/spec-merge.js +168 -0
  52. package/src/lib/swarm.js +2 -2
  53. package/src/lib/visual-qa.js +162 -9
  54. package/template/.agents/rules/03-gemstack-security.md +2 -2
  55. package/.github/workflows/main-ci.yml +0 -32
  56. package/.github/workflows/pr-ci.yml +0 -31
  57. package/.github/workflows/publish.yml +0 -52
  58. package/.github/workflows/release-readiness.yml +0 -43
  59. package/gemstack-ai-1.4.0.tgz +0 -0
@@ -0,0 +1,168 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Spec Conflict & Merge Engine (Gemstack 2.0 Sprint C)
5
+ * Detects contract and test ID collisions between branches/specs offline.
6
+ */
7
+
8
+ const { extractContractsBlock } = require('./contracts');
9
+ const { extractTestMatrixBlock } = require('./test-matrix');
10
+
11
+ /**
12
+ * Normalizes input which can be markdown string or parsed object.
13
+ * @param {string|object} specInput
14
+ * @returns {{ contracts: Array, tests: Array, requirements: Array }}
15
+ */
16
+ function normalizeSpecRepresentation(specInput) {
17
+ if (typeof specInput === 'object' && specInput !== null) {
18
+ return {
19
+ contracts: Array.isArray(specInput.contracts) ? specInput.contracts : [],
20
+ tests: Array.isArray(specInput.tests) ? specInput.tests : [],
21
+ requirements: Array.isArray(specInput.requirements) ? specInput.requirements : []
22
+ };
23
+ }
24
+
25
+ if (typeof specInput === 'string') {
26
+ const contractsRes = extractContractsBlock(specInput);
27
+ const contracts = !contractsRes.isLegacy ? contractsRes.contracts : [];
28
+
29
+ const matrixRes = extractTestMatrixBlock(specInput);
30
+ const tests = !matrixRes.isLegacy ? matrixRes.matrix : [];
31
+
32
+ return {
33
+ contracts,
34
+ tests,
35
+ requirements: []
36
+ };
37
+ }
38
+
39
+ return { contracts: [], tests: [], requirements: [] };
40
+ }
41
+
42
+ /**
43
+ * Detects conflicts between two specifications.
44
+ * @param {string|object} specA - Base or local spec
45
+ * @param {string|object} specB - Incoming or remote spec
46
+ * @returns {{ valid: boolean, conflicts: Array<{ type: string, id: string, reason: string, details?: any }> }}
47
+ */
48
+ function detectSpecConflicts(specA, specB) {
49
+ const repA = normalizeSpecRepresentation(specA);
50
+ const repB = normalizeSpecRepresentation(specB);
51
+
52
+ const conflicts = [];
53
+
54
+ // 1. Detect Contract Collisions
55
+ const contractsMapA = new Map(repA.contracts.map(c => [c.id, c]));
56
+ for (const cB of repB.contracts) {
57
+ if (contractsMapA.has(cB.id)) {
58
+ const cA = contractsMapA.get(cB.id);
59
+
60
+ // Check type collision
61
+ if (cA.type !== cB.type) {
62
+ conflicts.push({
63
+ type: 'CONTRACT_COLLISION',
64
+ id: cB.id,
65
+ reason: `Contract "${cB.id}" has conflicting types: "${cA.type}" vs "${cB.type}".`
66
+ });
67
+ continue;
68
+ }
69
+
70
+ // Check value collision based on type
71
+ if (cA.type === 'BOOLEAN_INVARIANT' && cA.value !== cB.value) {
72
+ conflicts.push({
73
+ type: 'CONTRACT_COLLISION',
74
+ id: cB.id,
75
+ reason: `Contract "${cB.id}" has conflicting boolean values: ${cA.value} vs ${cB.value}.`
76
+ });
77
+ } else if (cA.type === 'ENUM_SET') {
78
+ const setA = new Set(cA.values || []);
79
+ const setB = new Set(cB.values || []);
80
+ // If values differ materially and neither is empty
81
+ const diffA = [...setA].filter(x => !setB.has(x));
82
+ const diffB = [...setB].filter(x => !setA.has(x));
83
+ if (diffA.length > 0 || diffB.length > 0) {
84
+ conflicts.push({
85
+ type: 'CONTRACT_COLLISION',
86
+ id: cB.id,
87
+ reason: `Contract ENUM_SET "${cB.id}" has divergent enum values.`
88
+ });
89
+ }
90
+ } else {
91
+ // Generic deep check
92
+ if (JSON.stringify(cA) !== JSON.stringify(cB)) {
93
+ conflicts.push({
94
+ type: 'CONTRACT_COLLISION',
95
+ id: cB.id,
96
+ reason: `Contract "${cB.id}" has incompatible configuration definitions.`
97
+ });
98
+ }
99
+ }
100
+ }
101
+ }
102
+
103
+ // 2. Detect Test ID Collisions with divergent criteria
104
+ const testsMapA = new Map(repA.tests.map(t => [t.id, t]));
105
+ for (const tB of repB.tests) {
106
+ if (testsMapA.has(tB.id)) {
107
+ const tA = testsMapA.get(tB.id);
108
+ if (
109
+ tA.category !== tB.category ||
110
+ tA.layer !== tB.layer ||
111
+ tA.gate !== tB.gate ||
112
+ tA.description !== tB.description ||
113
+ tA.pass_criteria !== tB.pass_criteria
114
+ ) {
115
+ conflicts.push({
116
+ type: 'DUPLICATE_TEST_ID',
117
+ id: tB.id,
118
+ reason: `Canonical test ID "${tB.id}" is declared in both specs with divergent definitions.`
119
+ });
120
+ }
121
+ }
122
+ }
123
+
124
+ return {
125
+ valid: conflicts.length === 0,
126
+ conflicts
127
+ };
128
+ }
129
+
130
+ /**
131
+ * Merges two specifications if no conflicts exist.
132
+ * @param {string|object} specA
133
+ * @param {string|object} specB
134
+ * @returns {object} Merged representation
135
+ */
136
+ function mergeSpecs(specA, specB) {
137
+ const conflictReport = detectSpecConflicts(specA, specB);
138
+ if (!conflictReport.valid) {
139
+ const err = new Error(`Conflictos detectados al fusionar especificaciones: ${conflictReport.conflicts.map(c => `${c.type} (${c.id}): ${c.reason}`).join('; ')}`);
140
+ err.code = 'SPEC_MERGE_CONFLICT';
141
+ err.conflicts = conflictReport.conflicts;
142
+ throw err;
143
+ }
144
+
145
+ const repA = normalizeSpecRepresentation(specA);
146
+ const repB = normalizeSpecRepresentation(specB);
147
+
148
+ // Merge contracts (union by ID)
149
+ const contractsMap = new Map();
150
+ for (const c of repA.contracts) contractsMap.set(c.id, c);
151
+ for (const c of repB.contracts) contractsMap.set(c.id, c);
152
+
153
+ // Merge tests (union by ID)
154
+ const testsMap = new Map();
155
+ for (const t of repA.tests) testsMap.set(t.id, t);
156
+ for (const t of repB.tests) testsMap.set(t.id, t);
157
+
158
+ return {
159
+ contracts: Array.from(contractsMap.values()),
160
+ tests: Array.from(testsMap.values()),
161
+ requirements: [...repA.requirements, ...repB.requirements]
162
+ };
163
+ }
164
+
165
+ module.exports = {
166
+ detectSpecConflicts,
167
+ mergeSpecs
168
+ };
package/src/lib/swarm.js CHANGED
@@ -234,7 +234,7 @@ function planSwarmWaves(tasks) {
234
234
  const nextRemaining = [];
235
235
 
236
236
  for (const task of remaining) {
237
- const taskWrites = (task.write_set || []).map(normalizePath);
237
+ const taskWrites = (task.write_set || []).map(p => normalizePath(p));
238
238
  let collides = false;
239
239
 
240
240
  for (const p of taskWrites) {
@@ -283,7 +283,7 @@ function validateWritePartitions(wave) {
283
283
  const claimedPaths = new Map(); // path -> task_id
284
284
 
285
285
  for (const t of tasks) {
286
- const writes = (t.write_set || []).map(normalizePath);
286
+ const writes = (t.write_set || []).map(p => normalizePath(p));
287
287
  for (const p of writes) {
288
288
  if (claimedPaths.has(p)) {
289
289
  const otherTaskId = claimedPaths.get(p);
@@ -10,8 +10,9 @@
10
10
  const crypto = require('node:crypto');
11
11
  const fs = require('node:fs');
12
12
  const path = require('node:path');
13
- const { normalizePath } = require('./hasher');
13
+ const { normalizePath, hashFile } = require('./hasher');
14
14
  const { createFinding } = require('./findings');
15
+ const fssafe = require('./filesystem-safe');
15
16
 
16
17
  const VQA_SCHEMA_VERSION = '1.0.0';
17
18
 
@@ -266,14 +267,42 @@ function validateBaselineIntegrity(scenario, targetDir) {
266
267
  }
267
268
 
268
269
  /**
269
- * Compares live visual evidence against canonical baseline.
270
+ * Sanitizes and masks sensitive input fields and tokens before visual capture persistence.
271
+ *
272
+ * @param {string} content - HTML or DOM text
273
+ * @returns {string} Sanitized content with sensitive data masked
274
+ */
275
+ function maskSensitiveFieldsBeforeCapture(content) {
276
+ if (!content || typeof content !== 'string') return content;
277
+ let masked = content;
278
+
279
+ // 1. Password input values
280
+ masked = masked.replace(/(<input\b[^>]*\btype\s*=\s*["']?password["']?[^>]*\bvalue\s*=\s*["'])([^"']*)(["'])/gi, '$1[MASKED_PASSWORD]$3');
281
+ masked = masked.replace(/(<input\b[^>]*\bvalue\s*=\s*["'])([^"']*)(["'][^>]*\btype\s*=\s*["']?password["']?[^>]*>)/gi, '$1[MASKED_PASSWORD]$3');
282
+
283
+ // 2. Sensitive ids or names (password, token, secret, key, credit_card, card)
284
+ masked = masked.replace(/(<input\b[^>]*(?:\bid|\bname)\s*=\s*["']?[^"']*(?:password|token|secret|key|credit_card|card)[^"']*["']?[^>]*\bvalue\s*=\s*["'])([^"']*)(["'])/gi, '$1[MASKED_SENSITIVE]$3');
285
+ masked = masked.replace(/(<input\b[^>]*\bvalue\s*=\s*["'])([^"']*)(["'][^>]*(?:\bid|\bname)\s*=\s*["']?[^"']*(?:password|token|secret|key|credit_card|card)[^"']*["']?[^>]*>)/gi, '$1[MASKED_SENSITIVE]$3');
286
+
287
+ // 3. API Keys and Tokens in attributes or text
288
+ masked = masked.replace(/sk-(?:live_|proj-)?[a-zA-Z0-9_-]{10,}/g, '[MASKED_KEY]');
289
+
290
+ // 4. Credit card numbers (13-19 consecutive digits or grouped with spaces/dashes)
291
+ masked = masked.replace(/\b(?:\d{4}[ -]?){3,4}\d{1,4}\b/g, '[MASKED_CARD]');
292
+
293
+ return masked;
294
+ }
295
+
296
+ /**
297
+ * Compares live visual evidence against canonical baseline with honest disk recomputation.
270
298
  *
271
299
  * @param {object} scenario - Scenario definition
272
300
  * @param {object} evidence - Submitted evidence object
273
301
  * @param {string} targetDir - Repository target directory
302
+ * @param {object} [options={}] - Additional options (e.g. diffAdapter)
274
303
  * @returns {{ status: string, passed: boolean, diff_percentage: number, findings: Array<object> }}
275
304
  */
276
- function compareVisualEvidence(scenario, evidence, targetDir) {
305
+ function compareVisualEvidence(scenario, evidence, targetDir, options = {}) {
277
306
  const findings = [];
278
307
  const baseline = scenario.baseline;
279
308
 
@@ -299,8 +328,81 @@ function compareVisualEvidence(scenario, evidence, targetDir) {
299
328
  return { status: 'BASELINE_MISSING', passed: false, diff_percentage: 1.0, findings };
300
329
  }
301
330
 
302
- // Fast-path: SHA-256 match
303
- if (evidence.image_sha256 && baseline.image_sha256 && evidence.image_sha256 === baseline.image_sha256) {
331
+ let effectiveLiveHash = evidence.image_sha256;
332
+ let effectiveBaselineHash = baseline.image_sha256;
333
+
334
+ // Recompute hashes directly from disk files if targetDir and paths are available
335
+ if (targetDir) {
336
+ if (baseline.image_path) {
337
+ try {
338
+ const absBaselinePath = fssafe.resolveSafeStrict(targetDir, baseline.image_path);
339
+ if (fs.existsSync(absBaselinePath)) {
340
+ const diskBaselineHash = hashFile(absBaselinePath);
341
+ if (baseline.image_sha256 && diskBaselineHash !== baseline.image_sha256) {
342
+ findings.push(createFinding({
343
+ code: 'VQA_BASELINE_TAMPERED',
344
+ contractId: 'baseline-explicit-update-only',
345
+ phase: 'visual-qa',
346
+ location: baseline.image_path,
347
+ details: `Baseline image "${baseline.image_path}" was modified on disk. Recorded: ${baseline.image_sha256}, Actual: ${diskBaselineHash}.`
348
+ }));
349
+ return { status: 'BASELINE_TAMPERED', passed: false, diff_percentage: 1.0, findings };
350
+ }
351
+ effectiveBaselineHash = diskBaselineHash;
352
+ }
353
+ } catch (err) {
354
+ findings.push(createFinding({
355
+ code: 'VQA_BASELINE_TAMPERED',
356
+ contractId: 'baseline-explicit-update-only',
357
+ phase: 'visual-qa',
358
+ location: baseline.image_path,
359
+ details: `Error validating baseline path: ${err.message}`
360
+ }));
361
+ return { status: 'BASELINE_TAMPERED', passed: false, diff_percentage: 1.0, findings };
362
+ }
363
+ }
364
+
365
+ const livePathCandidate = evidence.live_screenshot_path || (evidence.image_path && fs.existsSync(fssafe.resolveSafe(targetDir, evidence.image_path)) ? evidence.image_path : null);
366
+ if (livePathCandidate) {
367
+ try {
368
+ const absLivePath = fssafe.resolveSafeStrict(targetDir, livePathCandidate);
369
+ if (!fs.existsSync(absLivePath)) {
370
+ findings.push(createFinding({
371
+ code: 'VQA_IMAGE_NOT_FOUND',
372
+ contractId: 'visual-evidence-subordinate',
373
+ phase: 'visual-qa',
374
+ location: scenario.scenario_id,
375
+ details: `Live screenshot file not found: ${livePathCandidate}`
376
+ }));
377
+ return { status: 'EVIDENCE_MISSING', passed: false, diff_percentage: 1.0, findings };
378
+ }
379
+ const diskLiveHash = hashFile(absLivePath);
380
+ if (evidence.image_sha256 && evidence.image_sha256 !== diskLiveHash) {
381
+ findings.push(createFinding({
382
+ code: 'VQA_EVIDENCE_HASH_MISMATCH',
383
+ contractId: 'visual-evidence-subordinate',
384
+ phase: 'visual-qa',
385
+ location: scenario.scenario_id,
386
+ details: `Submitted evidence image_sha256 (${evidence.image_sha256}) does not match disk file hash (${diskLiveHash}).`
387
+ }));
388
+ return { status: 'EVIDENCE_HASH_MISMATCH', passed: false, diff_percentage: 1.0, findings };
389
+ }
390
+ effectiveLiveHash = diskLiveHash;
391
+ } catch (err) {
392
+ findings.push(createFinding({
393
+ code: 'VQA_IMAGE_NOT_FOUND',
394
+ contractId: 'visual-evidence-subordinate',
395
+ phase: 'visual-qa',
396
+ location: scenario.scenario_id,
397
+ details: `Error validating live screenshot path: ${err.message}`
398
+ }));
399
+ return { status: 'EVIDENCE_MISSING', passed: false, diff_percentage: 1.0, findings };
400
+ }
401
+ }
402
+ }
403
+
404
+ // Fast-path: SHA-256 match from verified disk hashes
405
+ if (effectiveLiveHash && effectiveBaselineHash && effectiveLiveHash === effectiveBaselineHash) {
304
406
  return {
305
407
  status: 'PASS',
306
408
  passed: true,
@@ -309,11 +411,52 @@ function compareVisualEvidence(scenario, evidence, targetDir) {
309
411
  };
310
412
  }
311
413
 
312
- // Evaluate tolerances
414
+ // Evaluate tolerances and real diff adapter
313
415
  const maxDiff = (scenario.tolerances && scenario.tolerances.max_diff_percentage !== undefined)
314
416
  ? scenario.tolerances.max_diff_percentage
315
417
  : 0.00;
316
418
 
419
+ const diffAdapter = (options && options.diffAdapter) || (evidence && evidence.diffAdapter);
420
+
421
+ if (diffAdapter && typeof diffAdapter.computeDiff === 'function') {
422
+ let baselineBuf = null;
423
+ let liveBuf = null;
424
+ if (targetDir && baseline.image_path) {
425
+ try { baselineBuf = fs.readFileSync(fssafe.resolveSafeStrict(targetDir, baseline.image_path)); } catch {}
426
+ }
427
+ const liveRel = evidence.live_screenshot_path || evidence.image_path;
428
+ if (targetDir && liveRel) {
429
+ try { liveBuf = fs.readFileSync(fssafe.resolveSafeStrict(targetDir, liveRel)); } catch {}
430
+ }
431
+
432
+ const diffResult = diffAdapter.computeDiff(baselineBuf, liveBuf, scenario.tolerances);
433
+ const observedDiff = typeof diffResult.diff_percentage === 'number' ? diffResult.diff_percentage : 0.0;
434
+
435
+ if (observedDiff > maxDiff) {
436
+ findings.push(createFinding({
437
+ code: 'VQA_VISUAL_REGRESSION',
438
+ contractId: 'visual-evidence-subordinate',
439
+ phase: 'visual-qa',
440
+ location: scenario.scenario_id,
441
+ details: `Visual regression on "${scenario.scenario_id}": observed diff ${observedDiff} exceeds maximum allowed ${maxDiff}.`
442
+ }));
443
+ return {
444
+ status: 'VISUAL_REGRESSION',
445
+ passed: false,
446
+ diff_percentage: observedDiff,
447
+ findings
448
+ };
449
+ }
450
+
451
+ return {
452
+ status: 'PASS',
453
+ passed: true,
454
+ diff_percentage: observedDiff,
455
+ findings: []
456
+ };
457
+ }
458
+
459
+ // If no diff adapter is provided:
317
460
  const observedDiff = typeof evidence.diff_percentage === 'number' ? evidence.diff_percentage : 0.05;
318
461
 
319
462
  if (observedDiff > maxDiff) {
@@ -332,11 +475,20 @@ function compareVisualEvidence(scenario, evidence, targetDir) {
332
475
  };
333
476
  }
334
477
 
478
+ // When live screenshot differs from baseline and caller claims low diff without adapter: FAIL-CLOSED UNVERIFIED
479
+ findings.push(createFinding({
480
+ code: 'VQA_DIFF_ENGINE_UNAVAILABLE',
481
+ contractId: 'visual-evidence-subordinate',
482
+ phase: 'visual-qa',
483
+ location: scenario.scenario_id,
484
+ details: `Visual deviation detected on "${scenario.scenario_id}" but no diff engine adapter is available. Status UNVERIFIED.`
485
+ }));
486
+
335
487
  return {
336
- status: 'PASS',
337
- passed: true,
488
+ status: 'UNVERIFIED',
489
+ passed: false,
338
490
  diff_percentage: observedDiff,
339
- findings: []
491
+ findings
340
492
  };
341
493
  }
342
494
 
@@ -492,6 +644,7 @@ module.exports = {
492
644
  validateViewport,
493
645
  validateEnvironmentMetadata,
494
646
  applySelectorMasks,
647
+ maskSensitiveFieldsBeforeCapture,
495
648
  validateBaselineIntegrity,
496
649
  compareVisualEvidence,
497
650
  promoteVisualBaseline,
@@ -1,7 +1,7 @@
1
- # Gemstack Security Core (Military-Grade Shielding)
1
+ # Gemstack Security Core (Architecture & Security Gates)
2
2
 
3
3
  ## Propósito
4
- Esta es la Ley de "Seguridad por Diseño". Todo código escrito, planificado o revisado por la IA bajo el marco Gemstack DEBE adherirse a estos principios de blindaje, independientemente del stack tecnológico utilizado. El objetivo es mitigar el 99% de las vulnerabilidades comunes (OWASP) desde el momento de la concepción del código.
4
+ Esta es la Ley de "Seguridad por Diseño". Todo código escrito, planificado o revisado por la IA bajo el marco Gemstack DEBE adherirse a estos principios de blindaje, independientemente del stack tecnológico utilizado. El objetivo es establecer controles sistemáticos y verificables frente a vulnerabilidades comunes (OWASP Top 10) desde el momento de la concepción del código.
5
5
 
6
6
  ## 1. Cero Exposición de Credenciales (Zero Trust Secrets)
7
7
  - **Regla Estricta:** JAMÁS hardcodees contraseñas, llaves de API, secrets de Webhooks o URIs de bases de datos en el código fuente.
@@ -1,32 +0,0 @@
1
- name: Main CI
2
-
3
- on:
4
- push:
5
- branches:
6
- - main
7
-
8
- jobs:
9
- test:
10
- name: Full CI
11
- runs-on: ${{ matrix.os }}
12
- strategy:
13
- matrix:
14
- os: [ubuntu-latest, windows-latest, macos-latest]
15
- node-version: [18.x, 20.x]
16
- steps:
17
- - name: Checkout repository
18
- uses: actions/checkout@v4
19
- - name: Setup Node.js
20
- uses: actions/setup-node@v4
21
- with:
22
- node-version: ${{ matrix.node-version }}
23
- - name: Install Dependencies
24
- run: npm install
25
- - name: Run Native Tests
26
- run: npm test
27
- - name: Run CI Suite
28
- run: npm run ci:all
29
- - name: Run Demo Smoke
30
- run: npm run ci:demo
31
- - name: Validate Package Build
32
- run: npm run pack:dry
@@ -1,31 +0,0 @@
1
- name: PR CI
2
-
3
- on:
4
- pull_request:
5
- branches:
6
- - main
7
-
8
- jobs:
9
- test:
10
- name: Quick CI
11
- runs-on: ubuntu-latest
12
- strategy:
13
- matrix:
14
- node-version: [18.x, 20.x]
15
- steps:
16
- - name: Checkout repository
17
- uses: actions/checkout@v4
18
- - name: Setup Node.js
19
- uses: actions/setup-node@v4
20
- with:
21
- node-version: ${{ matrix.node-version }}
22
- - name: Install Dependencies
23
- run: npm install
24
- - name: Run Native Tests
25
- run: npm test
26
- - name: Run CI Suite
27
- run: npm run ci:all
28
- - name: Run Demo Smoke
29
- run: npm run ci:demo
30
- - name: Validate Package Build
31
- run: npm run pack:dry
@@ -1,52 +0,0 @@
1
- name: Publish Release
2
-
3
- on:
4
- push:
5
- tags:
6
- - 'v*'
7
-
8
- permissions:
9
- contents: write # Requerido para crear GitHub Releases
10
-
11
- jobs:
12
- publish:
13
- name: Publish to NPM and GitHub Releases
14
- runs-on: ubuntu-latest
15
-
16
- steps:
17
- - name: Checkout code
18
- uses: actions/checkout@v4
19
-
20
- - name: Setup Node.js
21
- uses: actions/setup-node@v4
22
- with:
23
- node-version: '20'
24
- registry-url: 'https://registry.npmjs.org'
25
-
26
- - name: Install Dependencies
27
- run: npm ci || npm install
28
-
29
- - name: Security Gate - Run CI All
30
- run: npm run ci:all
31
-
32
- - name: Pack Package
33
- run: npm pack
34
- id: pack
35
-
36
- - name: Publish to NPM
37
- run: npm publish --access public
38
- env:
39
- NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
40
-
41
- - name: Create GitHub Release
42
- env:
43
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
44
- run: |
45
- # Obtener el nombre del archivo tarball generado por npm pack
46
- TARBALL=$(ls gemstack-*.tgz)
47
-
48
- # Crear el Release oficial de GitHub usando el CLI nativo 'gh'
49
- gh release create ${{ github.ref_name }} \
50
- --title "Release ${{ github.ref_name }}" \
51
- --notes-file RELEASE_NOTES.md \
52
- $TARBALL
@@ -1,43 +0,0 @@
1
- name: Release Readiness
2
-
3
- on:
4
- workflow_dispatch:
5
-
6
- jobs:
7
- validate-and-pack:
8
- name: Validate and Pack
9
- runs-on: ubuntu-latest
10
- steps:
11
- - name: Checkout repository
12
- uses: actions/checkout@v4
13
- - name: Setup Node.js
14
- uses: actions/setup-node@v4
15
- with:
16
- node-version: 20.x
17
- - name: Install Dependencies
18
- run: npm install
19
- - name: Run Native Tests
20
- run: npm test
21
- - name: Run CI Suite
22
- run: npm run ci:all
23
- - name: Run Demo Smoke
24
- run: npm run ci:demo
25
- - name: Pack NPM Tarball
26
- run: npm pack
27
- - name: Test Tarball in Dummy Project
28
- run: |
29
- mkdir ../gemstack-dummy
30
- cd ../gemstack-dummy
31
- npm init -y
32
- npm install ../Gemstack/gemstack-*.tgz
33
- npx gemstack --help
34
- npx gemstack init --dry-run
35
- npx gemstack init --yes
36
- npx gemstack doctor
37
- npx gemstack update --dry-run
38
- - name: Upload NPM Tarball
39
- uses: actions/upload-artifact@v4
40
- with:
41
- name: gemstack-npm-tarball
42
- path: gemstack-*.tgz
43
- if-no-files-found: error
Binary file