gemstack-ai 1.0.1 → 1.1.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.
Files changed (46) hide show
  1. package/.agents/rules/01-gemstack-core.md +15 -0
  2. package/.agents/rules/02-gemstack-constitution.md +12 -1
  3. package/.agents/skills/gemstack-handoff/SKILL.md +2 -1
  4. package/.agents/skills/gemstack-plan/SKILL.md +2 -1
  5. package/.agents/skills/gemstack-review/SKILL.md +7 -5
  6. package/.agents/skills/gemstack-ship/SKILL.md +5 -0
  7. package/.agents/skills/gemstack-spec/SKILL.md +3 -2
  8. package/.agents/skills/gemstack-tasks/SKILL.md +4 -3
  9. package/.gemstack/state.json +20 -2
  10. package/CHANGELOG.md +52 -0
  11. package/MANUAL.md +4 -4
  12. package/README.md +36 -8
  13. package/RELEASE_NOTES.md +105 -0
  14. package/assets/logo.jpg +0 -0
  15. package/docs/architecture-consistency.md +144 -0
  16. package/gemstack-ai-1.1.2.tgz +0 -0
  17. package/handoff.md +27 -40
  18. package/package.json +3 -2
  19. package/scripts/ci/smoke-cli.js +1 -0
  20. package/specs/006-architecture-consistency-engine/.gemstack.json +9 -0
  21. package/specs/006-architecture-consistency-engine/plan.md +319 -0
  22. package/specs/006-architecture-consistency-engine/spec.md +179 -0
  23. package/specs/006-architecture-consistency-engine/tasks.md +532 -0
  24. package/specs/templates/plan.md +11 -0
  25. package/specs/templates/spec.md +17 -0
  26. package/specs/templates/tasks.md +1 -0
  27. package/src/cli.js +9 -4
  28. package/src/commands/verify.js +311 -0
  29. package/src/lib/contracts.js +388 -0
  30. package/src/lib/findings.js +227 -0
  31. package/src/lib/hasher.js +103 -0
  32. package/src/lib/state.js +143 -0
  33. package/src/mcp-server.js +1 -1
  34. package/template/.agents/rules/01-gemstack-core.md +15 -0
  35. package/template/.agents/rules/02-gemstack-constitution.md +12 -1
  36. package/template/.agents/skills/gemstack-handoff/SKILL.md +2 -1
  37. package/template/.agents/skills/gemstack-plan/SKILL.md +2 -1
  38. package/template/.agents/skills/gemstack-review/SKILL.md +7 -5
  39. package/template/.agents/skills/gemstack-ship/SKILL.md +5 -0
  40. package/template/.agents/skills/gemstack-spec/SKILL.md +3 -2
  41. package/template/.agents/skills/gemstack-tasks/SKILL.md +4 -3
  42. package/template/docs/architecture-consistency.md +144 -0
  43. package/template/specs/templates/plan.md +11 -0
  44. package/template/specs/templates/spec.md +17 -0
  45. package/template/specs/templates/tasks.md +1 -0
  46. package/gemstack-ai-1.0.1.tgz +0 -0
@@ -0,0 +1,227 @@
1
+ const crypto = require('node:crypto');
2
+ const { normalizePath } = require('./hasher');
3
+
4
+ /**
5
+ * Computes canonical 64-character lowercase hexadecimal SHA-256 fingerprint for a finding.
6
+ *
7
+ * @param {object} params
8
+ * @param {string} params.code - Finding error code
9
+ * @param {string|null} params.contractId - Optional contract id
10
+ * @param {string} params.phase - Phase where violation was found
11
+ * @param {string} params.location - File path of the violation
12
+ * @returns {string} 64-char lowercase hex SHA-256
13
+ */
14
+ function computeFindingFingerprint({ code, contractId, phase, location }) {
15
+ const normLocation = normalizePath(location);
16
+ const payload = JSON.stringify({
17
+ code: code || 'UNKNOWN_ERROR',
18
+ contractId: contractId ?? null,
19
+ phase: phase || 'unknown',
20
+ location: normLocation || null
21
+ });
22
+
23
+ return crypto.createHash('sha256').update(payload, 'utf8').digest('hex');
24
+ }
25
+
26
+ /**
27
+ * Formats a cosmetic display string for a fingerprint (12-char prefix).
28
+ * Never used for internal storage, indexing or anti-loop logic.
29
+ *
30
+ * @param {string} fingerprint
31
+ * @returns {string} 12-char display token
32
+ */
33
+ function formatDisplayFingerprint(fingerprint) {
34
+ if (typeof fingerprint !== 'string') return '';
35
+ return fingerprint.slice(0, 12);
36
+ }
37
+
38
+ /**
39
+ * Creates a structured Finding object.
40
+ *
41
+ * @param {object} params
42
+ * @returns {object} Finding
43
+ */
44
+ function createFinding({ code, contractId, phase, location, delta = null, details = null }) {
45
+ const fp = computeFindingFingerprint({ code, contractId, phase, location });
46
+ return {
47
+ fingerprint: fp,
48
+ display_id: formatDisplayFingerprint(fp),
49
+ code,
50
+ contractId: contractId ?? null,
51
+ phase,
52
+ location: normalizePath(location),
53
+ delta,
54
+ details,
55
+ status: 'OPEN',
56
+ is_blocking: true,
57
+ detected_at: new Date().toISOString(),
58
+ resolved_at: null
59
+ };
60
+ }
61
+
62
+ /**
63
+ * Computes the full frozen contextHash for an accepted exception.
64
+ * Context identity = SHA-256(upstreamAcceptedPhaseHash + currentComparedPhaseHash + normalizedContractRepresentation).
65
+ *
66
+ * @param {object} params
67
+ * @param {string} params.upstreamAcceptedPhaseHash
68
+ * @param {string} params.currentComparedPhaseHash
69
+ * @param {string} params.normalizedContractRepresentation - Deterministically serialized contract JSON
70
+ * @returns {string} 64-character lowercase hex SHA-256
71
+ */
72
+ function computeContextHash({ upstreamAcceptedPhaseHash, currentComparedPhaseHash, normalizedContractRepresentation }) {
73
+ const payload = JSON.stringify({
74
+ upstreamAcceptedPhaseHash: upstreamAcceptedPhaseHash || '',
75
+ currentComparedPhaseHash: currentComparedPhaseHash || '',
76
+ normalizedContractRepresentation: normalizedContractRepresentation || ''
77
+ });
78
+
79
+ return crypto.createHash('sha256').update(payload, 'utf8').digest('hex');
80
+ }
81
+
82
+ /**
83
+ * Reconciles findings between successive checks (Anti-Loop mechanism):
84
+ * - If an existing finding is no longer in current violations -> status becomes 'RESOLVED'.
85
+ * - If a finding was 'RESOLVED' but the violation recurs -> status reopens to 'OPEN'.
86
+ * - New violations are added as 'OPEN'.
87
+ *
88
+ * @param {Array<object>} existingFindings
89
+ * @param {Array<object>} currentViolations
90
+ * @returns {Array<object>} Reconciled findings
91
+ */
92
+ function reconcileFindings(existingFindings = [], currentViolations = []) {
93
+ const reconciled = [];
94
+ const currentViolationFps = new Map();
95
+
96
+ for (const v of currentViolations) {
97
+ const fp = computeFindingFingerprint({
98
+ code: v.code,
99
+ contractId: v.contractId,
100
+ phase: v.phase,
101
+ location: v.location
102
+ });
103
+ currentViolationFps.set(fp, v);
104
+ }
105
+
106
+ // Process existing findings
107
+ for (const f of existingFindings) {
108
+ if (currentViolationFps.has(f.fingerprint)) {
109
+ const v = currentViolationFps.get(f.fingerprint);
110
+ currentViolationFps.delete(f.fingerprint); // Handled
111
+
112
+ // If it was RESOLVED, reopen it (anti-loop protection)
113
+ if (f.status === 'RESOLVED') {
114
+ reconciled.push({
115
+ ...f,
116
+ status: 'OPEN',
117
+ is_blocking: true,
118
+ delta: v.delta,
119
+ detected_at: new Date().toISOString(),
120
+ resolved_at: null
121
+ });
122
+ } else {
123
+ reconciled.push({
124
+ ...f,
125
+ delta: v.delta,
126
+ is_blocking: f.status !== 'ACCEPTED_EXCEPTION'
127
+ });
128
+ }
129
+ } else {
130
+ // Violation not present in current analysis -> mark RESOLVED if it was OPEN
131
+ if (f.status === 'OPEN') {
132
+ reconciled.push({
133
+ ...f,
134
+ status: 'RESOLVED',
135
+ is_blocking: false,
136
+ resolved_at: new Date().toISOString()
137
+ });
138
+ } else {
139
+ reconciled.push(f);
140
+ }
141
+ }
142
+ }
143
+
144
+ // Any remaining current violations are new
145
+ for (const [fp, v] of currentViolationFps.entries()) {
146
+ reconciled.push(createFinding(v));
147
+ }
148
+
149
+ return reconciled;
150
+ }
151
+
152
+ /**
153
+ * Evaluates accepted exceptions against current findings.
154
+ * If an exception matches the finding fingerprint AND has an identical full contextHash,
155
+ * it suppresses the blocker (is_blocking: false, status: 'ACCEPTED_EXCEPTION').
156
+ * If contextHash differs, the exception does NOT suppress and finding remains OPEN / blocking.
157
+ *
158
+ * @param {Array<object>} findings
159
+ * @param {Array<object>} acceptedExceptions
160
+ * @param {object} currentContext - { upstreamAcceptedPhaseHash, currentComparedPhaseHash, normalizedContractRepresentation }
161
+ * @returns {Array<object>} Evaluated findings
162
+ */
163
+ function evaluateAcceptedExceptions(findings = [], acceptedExceptions = [], currentContext = {}) {
164
+ const currentContextHash = computeContextHash(currentContext);
165
+
166
+ const exceptionMap = new Map();
167
+ for (const ex of acceptedExceptions) {
168
+ if (ex && ex.fingerprint) {
169
+ exceptionMap.set(ex.fingerprint, ex);
170
+ }
171
+ }
172
+
173
+ return findings.map(f => {
174
+ if (exceptionMap.has(f.fingerprint)) {
175
+ const ex = exceptionMap.get(f.fingerprint);
176
+ if (ex.contextHash === currentContextHash) {
177
+ return {
178
+ ...f,
179
+ status: 'ACCEPTED_EXCEPTION',
180
+ is_blocking: false,
181
+ exceptionReason: ex.reason || 'Approved exception'
182
+ };
183
+ } else {
184
+ // Context mutated -> exception invalidated
185
+ return {
186
+ ...f,
187
+ status: 'OPEN',
188
+ is_blocking: true,
189
+ exceptionInvalidated: true
190
+ };
191
+ }
192
+ }
193
+ return f;
194
+ });
195
+ }
196
+
197
+ /**
198
+ * Marks findings as SUPERSEDED if associated contracts were formally amended or removed.
199
+ *
200
+ * @param {Array<object>} findings
201
+ * @param {Array<string>} activeContractIds
202
+ * @returns {Array<object>}
203
+ */
204
+ function markSupersededFindings(findings = [], activeContractIds = []) {
205
+ const activeSet = new Set(activeContractIds);
206
+ return findings.map(f => {
207
+ if (f.contractId && !activeSet.has(f.contractId)) {
208
+ return {
209
+ ...f,
210
+ status: 'SUPERSEDED',
211
+ is_blocking: false,
212
+ superseded_at: new Date().toISOString()
213
+ };
214
+ }
215
+ return f;
216
+ });
217
+ }
218
+
219
+ module.exports = {
220
+ computeFindingFingerprint,
221
+ formatDisplayFingerprint,
222
+ createFinding,
223
+ computeContextHash,
224
+ reconcileFindings,
225
+ evaluateAcceptedExceptions,
226
+ markSupersededFindings
227
+ };
@@ -0,0 +1,103 @@
1
+ const crypto = require('node:crypto');
2
+ const fs = require('node:fs');
3
+ const path = require('node:path');
4
+
5
+ /**
6
+ * Normalizes content for canonical hashing:
7
+ * - Checks and rejects UTF-8 BOM by throwing CONTRACT_PARSE_ERROR
8
+ * - Normalizes CRLF and lone CR to standard LF
9
+ * Does NOT trim or alter whitespace/markdown/json formatting.
10
+ *
11
+ * @param {string} content
12
+ * @returns {string} normalized string
13
+ */
14
+ function normalizeContent(content) {
15
+ if (typeof content !== 'string') {
16
+ throw new TypeError('Content must be a string');
17
+ }
18
+
19
+ // Reject UTF-8 BOM
20
+ if (content.charCodeAt(0) === 0xFEFF) {
21
+ const err = new Error('UTF-8 BOM is forbidden in phase artifacts.');
22
+ err.code = 'CONTRACT_PARSE_ERROR';
23
+ throw err;
24
+ }
25
+
26
+ // Normalize newlines: CRLF -> LF, then lone CR -> LF
27
+ return content.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
28
+ }
29
+
30
+ /**
31
+ * Computes canonical 64-character lowercase hexadecimal SHA-256 hash of normalized content.
32
+ *
33
+ * @param {string} content
34
+ * @returns {string} 64-char lowercase hex SHA-256
35
+ */
36
+ function hashContent(content) {
37
+ const normalized = normalizeContent(content);
38
+ return crypto.createHash('sha256').update(normalized, 'utf8').digest('hex');
39
+ }
40
+
41
+ /**
42
+ * Reads a file as UTF-8 and computes its canonical hash.
43
+ *
44
+ * @param {string} filePath
45
+ * @returns {string} 64-char lowercase hex SHA-256
46
+ */
47
+ function hashFile(filePath) {
48
+ const raw = fs.readFileSync(filePath, 'utf8');
49
+ return hashContent(raw);
50
+ }
51
+
52
+ const WINDOWS_DRIVE_RE = /^[A-Za-z]:[\\/]/;
53
+ const WINDOWS_UNC_RE = /^\\\\[^\\/]+[\\/][^\\/]+/;
54
+
55
+ /**
56
+ * Checks if a path string has Windows structure (drive letter, UNC, or backslashes).
57
+ *
58
+ * @param {string} p
59
+ * @returns {boolean}
60
+ */
61
+ function isWindowsPath(p) {
62
+ if (typeof p !== 'string') return false;
63
+ return WINDOWS_DRIVE_RE.test(p) || WINDOWS_UNC_RE.test(p) || p.includes('\\');
64
+ }
65
+
66
+ /**
67
+ * Normalizes file paths to repository-relative POSIX format (using forward slashes '/').
68
+ * Uses explicit path.win32 or path.posix based on input path flavor rather than host OS.
69
+ *
70
+ * @param {string} filePath
71
+ * @param {string} [rootPath] - optional repository root
72
+ * @returns {string} normalized POSIX relative path
73
+ */
74
+ function normalizePath(filePath, rootPath) {
75
+ if (!filePath) return '';
76
+ if (!rootPath) {
77
+ return filePath.replace(/\\/g, '/');
78
+ }
79
+
80
+ const fileIsWindows = isWindowsPath(filePath);
81
+ const rootIsWindows = isWindowsPath(rootPath);
82
+
83
+ const isPosixAbs = (p) => typeof p === 'string' && p.startsWith('/') && !WINDOWS_DRIVE_RE.test(p);
84
+ if ((fileIsWindows && isPosixAbs(rootPath)) || (isPosixAbs(filePath) && rootIsWindows)) {
85
+ throw new Error(`Incompatible mixed path flavors: filePath="${filePath}", rootPath="${rootPath}"`);
86
+ }
87
+
88
+ const impl = (fileIsWindows || rootIsWindows) ? path.win32 : path.posix;
89
+ const rel = impl.relative(rootPath, filePath);
90
+
91
+ if (rel.startsWith('..\\') || rel.startsWith('../') || rel === '..') {
92
+ throw new Error(`Path "${filePath}" is outside root directory "${rootPath}"`);
93
+ }
94
+
95
+ return rel.replace(/\\/g, '/');
96
+ }
97
+
98
+ module.exports = {
99
+ normalizeContent,
100
+ hashContent,
101
+ hashFile,
102
+ normalizePath
103
+ };
@@ -0,0 +1,143 @@
1
+ const fs = require('node:fs');
2
+ const path = require('node:path');
3
+
4
+ /**
5
+ * Reads .gemstack/state.json with legacy fallback defaults.
6
+ * Preserves all unknown existing fields.
7
+ *
8
+ * @param {string} rootPath - Workspace root path
9
+ * @returns {object} State object
10
+ */
11
+ function readState(rootPath) {
12
+ const statePath = path.join(rootPath, '.gemstack', 'state.json');
13
+ if (!fs.existsSync(statePath)) {
14
+ return {
15
+ version: '0.1',
16
+ current_phase: null,
17
+ status: null,
18
+ stop_reason: null,
19
+ active_spec: null,
20
+ completed_phases: [],
21
+ phase_hashes: null,
22
+ consistency: null,
23
+ guard_mode: { careful: false, freeze: false, allowed_paths: [] }
24
+ };
25
+ }
26
+
27
+ try {
28
+ const raw = fs.readFileSync(statePath, 'utf8');
29
+ const parsed = JSON.parse(raw);
30
+ const { findings, accepted_exceptions, ...cleanParsed } = parsed;
31
+ return {
32
+ version: cleanParsed.version || '0.1',
33
+ current_phase: cleanParsed.current_phase ?? null,
34
+ status: cleanParsed.status ?? null,
35
+ stop_reason: cleanParsed.stop_reason ?? null,
36
+ active_spec: cleanParsed.active_spec ?? null,
37
+ completed_phases: Array.isArray(cleanParsed.completed_phases) ? cleanParsed.completed_phases : [],
38
+ phase_hashes: cleanParsed.phase_hashes ?? null,
39
+ consistency: cleanParsed.consistency ?? null,
40
+ guard_mode: cleanParsed.guard_mode || { careful: false, freeze: false, allowed_paths: [] },
41
+ ...cleanParsed // preserve any extra operational fields (excluding findings / accepted_exceptions)
42
+ };
43
+ } catch (err) {
44
+ throw new Error(`Failed to parse .gemstack/state.json: ${err.message}`);
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Writes an object atomically to disk by writing to a temporary file in the same directory
50
+ * and renaming it over the destination. Bounded retry for Windows locks.
51
+ *
52
+ * @param {string} filePath - Absolute path to destination file
53
+ * @param {object} data - Object to serialize
54
+ */
55
+ function writeJsonAtomic(filePath, data) {
56
+ const dir = path.dirname(filePath);
57
+ if (!fs.existsSync(dir)) {
58
+ fs.mkdirSync(dir, { recursive: true });
59
+ }
60
+
61
+ const tmpPath = `${filePath}.tmp.${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
62
+ const serialized = JSON.stringify(data, null, 2) + '\n';
63
+ fs.writeFileSync(tmpPath, serialized, 'utf8');
64
+
65
+ // Bounded retry for Windows transient file locks
66
+ let attempts = 0;
67
+ const maxAttempts = 5;
68
+ while (attempts < maxAttempts) {
69
+ try {
70
+ fs.renameSync(tmpPath, filePath);
71
+ return;
72
+ } catch (err) {
73
+ attempts++;
74
+ if (attempts >= maxAttempts) {
75
+ // cleanup tmp
76
+ try { fs.unlinkSync(tmpPath); } catch (_) {}
77
+ throw err;
78
+ }
79
+ // Busy wait short sleep
80
+ const start = Date.now();
81
+ while (Date.now() - start < 20) {}
82
+ }
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Writes .gemstack/state.json atomically.
88
+ * Strips historical findings and accepted_exceptions to enforce persistence boundary.
89
+ *
90
+ * @param {string} rootPath - Workspace root path
91
+ * @param {object} stateObj - State data
92
+ */
93
+ function writeStateAtomic(rootPath, stateObj) {
94
+ const statePath = path.join(rootPath, '.gemstack', 'state.json');
95
+ const { findings, accepted_exceptions, ...operationalState } = stateObj;
96
+ writeJsonAtomic(statePath, operationalState);
97
+ }
98
+
99
+ /**
100
+ * Reads feature sidecar .gemstack.json with safe defaults.
101
+ *
102
+ * @param {string} featureDir - Path to specs/<feature>
103
+ * @returns {object} Sidecar data
104
+ */
105
+ function readSidecar(featureDir) {
106
+ const sidecarPath = path.join(featureDir, '.gemstack.json');
107
+ if (!fs.existsSync(sidecarPath)) {
108
+ return {
109
+ phase_hashes: {},
110
+ historical_findings: [],
111
+ accepted_exceptions: []
112
+ };
113
+ }
114
+ try {
115
+ const raw = fs.readFileSync(sidecarPath, 'utf8');
116
+ return JSON.parse(raw);
117
+ } catch (err) {
118
+ return {
119
+ phase_hashes: {},
120
+ historical_findings: [],
121
+ accepted_exceptions: []
122
+ };
123
+ }
124
+ }
125
+
126
+ /**
127
+ * Writes feature sidecar .gemstack.json atomically.
128
+ *
129
+ * @param {string} featureDir - Path to specs/<feature>
130
+ * @param {object} sidecarObj - Data to write
131
+ */
132
+ function writeSidecarAtomic(featureDir, sidecarObj) {
133
+ const sidecarPath = path.join(featureDir, '.gemstack.json');
134
+ writeJsonAtomic(sidecarPath, sidecarObj);
135
+ }
136
+
137
+ module.exports = {
138
+ readState,
139
+ writeStateAtomic,
140
+ readSidecar,
141
+ writeSidecarAtomic,
142
+ writeJsonAtomic
143
+ };
package/src/mcp-server.js CHANGED
@@ -7,7 +7,7 @@
7
7
  const fs = require('fs');
8
8
  const path = require('path');
9
9
  const readline = require('readline');
10
- const manifest = require('../lib/manifest');
10
+ const manifest = require('./lib/manifest');
11
11
 
12
12
  // MCP Protocol structures
13
13
  function sendResponse(id, result, error = null) {
@@ -13,6 +13,7 @@ Eres Antigravity operando bajo el framework **Gemstack**, una metodología local
13
13
  2. **Aprobación para Deploy/Push:** Nunca hagas `git push`, merge o deploy sin aprobación explícita.
14
14
  3. **Handoff Inmutable:** NUNCA borres la sección "Intentos fallidos" de `handoff.md`. Si crece demasiado, mueve de forma segura el contenido antiguo a `handoff_archive.md`.
15
15
  4. **Dependencias y Auth:** Cambios a la arquitectura de autenticación, migraciones de base de datos o instalación de dependencias globales requieren generación de plan técnico y aprobación humana.
16
+ 5. **Consistencia de Arquitectura y Congelamiento de Fases (Upgrade A):** Las decisiones arquitectónicas congeladas en `gemstack-contracts` no pueden ser contradichas silenciosamente por fases posteriores. Toda enmienda requiere aprobación humana explícita.
16
17
 
17
18
  ## Pseudo-Comandos (Ruteo Obligatorio)
18
19
  Si el usuario empieza su mensaje con uno de estos comandos, **NO improvises. DEBES cargar o seguir el skill correspondiente**:
@@ -49,3 +50,17 @@ Si el usuario empieza su mensaje con uno de estos comandos, **NO improvises. DEB
49
50
  - `/guard` -> Invoca `gemstack-guard`
50
51
  - `/unfreeze` -> Invoca `gemstack-guard`
51
52
 
53
+ ## Ruteo Semántico por Intención (Intent-Based Routing)
54
+ Si el usuario interactúa en lenguaje natural sin usar un `/comando` explícito, DEBES detectar la intención subyacente y activar el protocolo correspondiente:
55
+ 1. **Nueva funcionalidad o módulo mayor sin spec activa:**
56
+ - Si pide crear o agregar una feature sustancial (ej. "pon una parte para editar paquetes", "vamos a agregar cobro bimoneda"), **NO saltes directo al código**. Activa `gemstack-spec` para definir requisitos y criterios de éxito antes de implementar.
57
+ 2. **Solicitud de pruebas o validación:**
58
+ - Si el usuario dice "haz pruebas", "valida lo hecho", "comprueba que funcione" o "verifica los cambios", activa `gemstack-qa`.
59
+ 3. **Reporte de error o bug:**
60
+ - Si el usuario reporta que algo falló o no funciona como se esperaba, activa `gemstack-investigate` (principio: *No fixes before investigation*).
61
+ 4. **Cierre o pausa de sesión:**
62
+ - Si el usuario indica "terminamos por hoy", "voy a pausar", "dejo esto listo" o "prepara el resumen", activa `gemstack-handoff`.
63
+ 5. **Auditoría de seguridad:**
64
+ - Si pide revisar seguridad, permisos, tokens o vulnerabilidades, activa `gemstack-cso`.
65
+ 6. **Entrega o preparación de release:**
66
+ - Si pide preparar el merge, PR o entrega formal de la feature terminada, activa `gemstack-ship`.
@@ -14,11 +14,16 @@ Toda nueva funcionalidad DEBE nacer y estar estructurada preferentemente como un
14
14
  ## Article II: CLI / Interface Mandate
15
15
  Cada módulo o librería clave debe tener una forma de probarse e interactuar textualmente (CLI, scripts independientes, peticiones directas de texto o JSON). Evita componentes opacos que solo puedan probarse levantando interfaces gráficas complejas.
16
16
 
17
- ## Article III: Test-First Imperative (NON-NEGOTIABLE)
17
+ ## Article III: Test-First Imperative & Zero Silent Failures (NON-NEGOTIABLE)
18
18
  NUNCA escribas el código de implementación antes que los tests (TDD).
19
19
  1. Escribe los tests (unitarios, de integración o contratos) basándote en la especificación.
20
20
  2. Si es posible, demuestra que fallan.
21
21
  3. Solo entonces, escribe la implementación real.
22
+ 4. **Zero Silent Failures (Multiplataforma Windows / Linux / macOS):**
23
+ - PROHIBIDO el uso de operadores de supresión de shell que enmascaren fallos en scripts de `test` de `package.json` (ej. `2>nul`, `2>/dev/null || true`).
24
+ - El script de test DEBE terminar con exit code distinto de cero si ocurre cualquier falla.
25
+ - En monorepos TypeScript, usa runners multiplataforma estandarizados (ej. `tsx --test src/**/*.test.ts tests/**/*.test.ts`, `node --test`, `vitest` o `jest`).
26
+ - El test runner debe validar que la suite ejecutó efectivamente pruebas (`tests > 0`). Un reporte de 0 tests ejecutados finalizando con exit code 0 es considerado un falso positivo inaceptable.
22
27
 
23
28
  ## Article IV: Zero Assumptions (No Hallucinations)
24
29
  Si un requerimiento del humano es vago, la IA NO debe adivinar.
@@ -42,3 +47,9 @@ Usa las funciones del framework y librerías estándar nativamente en lugar de c
42
47
 
43
48
  ## Article IX: Integration-First Testing
44
49
  Prioriza el testing realista. Si puedes probar el contrato real o la base de datos local real (con un entorno temporal) por encima de mocks complejos, hazlo. El código generado debe funcionar en la práctica.
50
+
51
+ ## Article X: Architecture Consistency & Immutability Gate (Upgrade A)
52
+ Las decisiones arquitectónicas congeladas no pueden ser contradichas silenciosamente por fases posteriores.
53
+ - Antes de avanzar de fase, el hash del artefacto aguas arriba debe ser válido, los contratos compatibles y la cantidad de bloqueadores deterministas abiertos debe ser exactamente 0.
54
+ - Los artefactos de fases aprobadas quedan sellados criptográficamente (SHA-256) y no pueden mutar sin enmienda explícita aprobada por humanos.
55
+ - Proyectos preexistentes sin bloques de contratos operan en modo LEGACY transparente sin fallas.
@@ -17,5 +17,6 @@ Esta habilidad se ejecuta cuando el usuario pide terminar la sesión, invoca `/h
17
17
  5. **ARCHIVADO SEGURO:** Si "Intentos fallidos" en `handoff.md` tiene demasiados puntos (más de 10-15), CORTA los elementos más antiguos y pégalos en `handoff_archive.md` bajo su lista, conservando solo los 3-5 intentos recientes en `handoff.md`.
18
18
  6. Define los "5. Próximos pasos" exactos para la próxima sesión.
19
19
  7. Guarda los cambios en `handoff.md` (y `handoff_archive.md` si fue necesario).
20
- 8. Despídete del usuario indicando que el handoff está listo.
20
+ 8. **Sincronización de Estado:** Verifica `.gemstack/state.json` asegurando que `current_phase` refleje con precisión si hay una spec activa o en espera, y actualiza `last_update` con la marca temporal actual.
21
+ 9. Despídete del usuario indicando que el handoff está listo.
21
22
 
@@ -16,5 +16,6 @@ Invocado mediante `/plan`.
16
16
  4. Genera `specs/[nombre-feature]/plan.md` usando `specs/templates/plan.md`.
17
17
  5. Detalla el stack y llena la tabla "Complexity Tracking" SÓLO si rompiste alguna regla de la constitución y necesitas justificarlo.
18
18
  6. Opcionalmente, genera los entregables satélites: `data-model.md`, `contracts/` (para APIs/Interfaces), y `quickstart.md`.
19
- 7. Pide aprobación al usuario antes de permitir la ejecución de `/tasks`.
19
+ 7. **Herencia y Contratos Aditivos (Upgrade A)**: PLAN hereda automáticamente los contratos declarados en `spec.md`. No contradigas ni alteres los contratos congelados heredados; si requieres contratos técnicos adicionales, decláralos de forma compatible y aditiva en el bloque ````gemstack-contracts ```` de `plan.md`.
20
+ 8. Pide aprobación al usuario antes de permitir la ejecución de `/tasks`.
20
21
 
@@ -11,8 +11,10 @@ Invocado mediante `/review`.
11
11
 
12
12
  ## Proceso:
13
13
  1. Analiza el código modificado (git diff, o archivos editados).
14
- 2. Verifica que las convenciones arquitectónicas del proyecto se respeten.
15
- 3. VERIFICACIÓN DE SEGURIDAD: Revisa obligatoriamente `.agents/rules/03-gemstack-security.md` para garantizar que el código propuesto no introduzca brechas de seguridad (IDOR, XSS, tokens expuestos).
16
- 4. Verifica que los tests cubran adecuadamente los cambios.
17
- 5. Emite sugerencias o aplica correcciones automáticas si son triviales.
18
- 6. Si el código está listo, sugiere `/qa` o `/ship`.
14
+ 2. **Validación Determinista Primero (Upgrade A)**: Ejecuta `node src/cli.js verify` o comprueba contratos congelados, hashes de fase y bloqueadores antes de proceder a la revisión semántica.
15
+ 3. Proporciona al revisor la lista de contratos efectivos, hashes de fase y hallazgos deterministas para no forzarlo a redescubrir desviaciones mecánicas.
16
+ 4. Verifica que las convenciones arquitectónicas del proyecto se respeten.
17
+ 5. VERIFICACIÓN DE SEGURIDAD: Revisa obligatoriamente `.agents/rules/03-gemstack-security.md` para garantizar que el código propuesto no introduzca brechas de seguridad (IDOR, XSS, tokens expuestos).
18
+ 6. Verifica que los tests cubran adecuadamente los cambios.
19
+ 7. Emite sugerencias o aplica correcciones automáticas si son triviales.
20
+ 8. Si el código está listo, sugiere `/qa` o `/ship`.
@@ -16,4 +16,9 @@ Invocado mediante `/ship`.
16
16
  4. Genera un PR summary si se pide.
17
17
  5. NO hagas push, merge o deploy sin aprobación explícita.
18
18
  6. Sugiere ejecutar `/handoff` para documentar la entrega en la memoria del proyecto.
19
+ 7. **Sincronización de Estado:** Actualiza `.gemstack/state.json`:
20
+ - Establece `"active_spec": null`
21
+ - Registra `"last_completed_feature": "specs/[nombre-feature]/"`
22
+ - Establece `"current_phase": "shipped"`
23
+ - Actualiza `"last_update"` con el timestamp ISO actual.
19
24
 
@@ -19,6 +19,7 @@ Eres un Product Manager técnico. Tu objetivo es convertir ideas vagas en requis
19
19
  4. El documento DEBE incluir: Historias de usuario priorizadas (P1, P2) que sean independientemente testeables, Criterios de Éxito medibles, y Casos Extremos.
20
20
  5. **CERO SUPOSICIONES**: Si el usuario omitió detalles, NO adivines. Usa el marcador `[NEEDS CLARIFICATION: tu duda]` en el documento.
21
21
  6. No describas implementación técnica (nada de stacks, bases de datos o APIs). Concéntrate estrictamente en el "Qué" y "Por qué".
22
- 7. Actualiza el archivo `.gemstack/state.json` para reflejar la rama activa: `{"active_spec": "specs/[nombre-feature]/"}` y el timestamp.
23
- 8. Una vez finalizado, indica al usuario que puede revisar la especificación y, tras resolver las dudas, ejecutar `/plan`.
22
+ 7. **Contratos Arquitectónicos Congelados (Upgrade A)**: Si la funcionalidad implica decisiones estructurales críticas (estados enum, tuplas de identidad, reglas de procedencia, invariantes booleanas, límites de roadmap o límites boundary), decláralos explícitamente en el bloque canónico ````gemstack-contracts ````. Congela solo decisiones materiales, no texto arbitrario.
23
+ 8. Actualiza el archivo `.gemstack/state.json` para reflejar la rama activa y fase: `{"active_spec": "specs/[nombre-feature]/", "current_phase": "spec", "last_update": "<timestamp>"}`.
24
+ 9. Una vez finalizado, indica al usuario que puede revisar la especificación y, tras resolver las dudas, ejecutar `/plan`.
24
25
 
@@ -12,7 +12,8 @@ Invocado mediante `/tasks`.
12
12
  ## Proceso:
13
13
  1. Lee `specs/[nombre-feature]/plan.md` y, si existen, `data-model.md` y la carpeta `contracts/`.
14
14
  2. Convierte los contratos, entidades y el plan en una lista estricta de ejecución en `specs/[nombre-feature]/tasks.md` usando la plantilla `specs/templates/tasks.md`.
15
- 3. Aplica Test-First: Las tareas de escribir pruebas (y validarlas) deben ir ANTES que la implementación de código.
16
- 4. Usa el marcador `[P]` para tareas independientes que se puedan paralelizar.
17
- 5. Ofrece al usuario comenzar automáticamente con la primera tarea o delegar a subagentes paralelos si hay múltiples `[P]`.
15
+ 3. **Herencia de Contratos (Upgrade A)**: TASKS hereda automáticamente los contratos consolidados de SPEC y PLAN. No se requiere declarar un bloque de contratos propio salvo que se agreguen contratos operacionales específicos de tareas.
16
+ 4. Aplica Test-First: Las tareas de escribir pruebas (y validarlas) deben ir ANTES que la implementación de código.
17
+ 5. Usa el marcador `[P]` para tareas independientes que se puedan paralelizar.
18
+ 6. Ofrece al usuario comenzar automáticamente con la primera tarea o delegar a subagentes paralelos si hay múltiples `[P]`.
18
19