refacil-sdd-ai 5.3.0 → 5.3.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/README.md CHANGED
@@ -277,6 +277,7 @@ All invoked as `/refacil:<name>` in Claude Code, Cursor, OpenCode, or Codex.
277
277
  | `/refacil:bug` | Full bugfix flow with regression tests |
278
278
  | `/refacil:update` | Detect and apply pending methodology migrations to the current repo |
279
279
  | `/refacil:stats` | Show change progress, task status, review gate, and test commands from SDD artifacts |
280
+ | `/refacil:status` | Show which phase of the SDD-AI cycle a change is in and the exact command to resume it |
280
281
  | `/refacil:read-spec` | Listen to change specs in the browser with on-device TTS |
281
282
  | `/refacil:autopilot` | Autonomous pipeline: chains apply → test → verify → review → archive in one invocation; up-code (push + PR) is optional and configured in pre-flight. Optional WhatsApp notification via `~/.refacil-sdd-ai/kapso.env` |
282
283
 
@@ -196,6 +196,16 @@ If the change involves a contract with another system (external API, event, queu
196
196
 
197
197
  If the input comes from a bus room agreement, still generate all artifacts in full according to the SDD-AI methodology. See `METHODOLOGY-CONTRACT.md` and `BUS-CROSS-REPO.md` (room agreements section).
198
198
 
199
+ ### Step 2.5: Record proposed state
200
+
201
+ After writing all four artifacts, run:
202
+
203
+ ```bash
204
+ refacil-sdd-ai sdd set-memory <changeName> --state proposed --actor proposer
205
+ ```
206
+
207
+ This records the initial state in `memory.yaml` for cross-skill tracking. If the command fails (e.g. the change directory does not exist yet at that point), skip silently — it must not block the flow.
208
+
199
209
  ### Step 3: Report + JSON block
200
210
 
201
211
  Your final response MUST have this structure:
@@ -183,6 +183,88 @@ function serializeMemoryYaml(obj) {
183
183
  return lines.join('\n') + '\n';
184
184
  }
185
185
 
186
+ // --- State tracking ---
187
+
188
+ const VALID_STATES = [
189
+ 'proposed',
190
+ 'approved',
191
+ 'apply-in-progress',
192
+ 'applied',
193
+ 'tested',
194
+ 'verified',
195
+ 'reviewed',
196
+ 'archived',
197
+ ];
198
+
199
+ /**
200
+ * Infers the current state of a change from its artifacts when no explicit
201
+ * currentState is stored in memory. Retrocompatible — never writes to disk.
202
+ *
203
+ * Priority (descending):
204
+ * 1. .review-passed exists → 'reviewed'
205
+ * 2. lastStep === 'verify' or 'verified' → 'verified'
206
+ * 3. lastStep === 'test' → 'tested'
207
+ * 4. lastStep === 'apply' → 'applied'
208
+ * 5. tasks.md has at least one [x] and lastStep is absent → 'apply-in-progress'
209
+ * 6. only proposal.md exists → 'proposed'
210
+ * 7. none of the above → 'unknown'
211
+ *
212
+ * NOTE — 'approved' is intentionally absent from this table.
213
+ * There is no artifact signal that distinguishes an approved proposal from a
214
+ * merely proposed one: both have proposal.md, specs.md, design.md, tasks.md.
215
+ * 'approved' is set exclusively via `set-memory --state approved --actor propose-skill`
216
+ * when the human approves the artifacts in /refacil:propose Step 4.
217
+ * Inference can only detect 'proposed' (artifact exists but no explicit state recorded).
218
+ *
219
+ * @param {string} changeDir Absolute path to the change directory.
220
+ * @param {object} memory Parsed memory.yaml object (may be empty).
221
+ * @returns {{ currentState: string, inferred: boolean }}
222
+ */
223
+ function inferCurrentState(changeDir, memory) {
224
+ // 1. .review-passed
225
+ if (fs.existsSync(path.join(changeDir, '.review-passed'))) {
226
+ return { currentState: 'reviewed', inferred: true };
227
+ }
228
+
229
+ const lastStep = memory.lastStep || null;
230
+
231
+ // 2. lastStep verify/verified
232
+ if (lastStep === 'verify' || lastStep === 'verified') {
233
+ return { currentState: 'verified', inferred: true };
234
+ }
235
+
236
+ // 3. lastStep test
237
+ if (lastStep === 'test') {
238
+ return { currentState: 'tested', inferred: true };
239
+ }
240
+
241
+ // 4. lastStep apply
242
+ if (lastStep === 'apply') {
243
+ return { currentState: 'applied', inferred: true };
244
+ }
245
+
246
+ // 5. tasks.md has at least one [x] and lastStep absent
247
+ const tasksPath = path.join(changeDir, 'tasks.md');
248
+ if (!lastStep && fs.existsSync(tasksPath)) {
249
+ try {
250
+ const tasksContent = fs.readFileSync(tasksPath, 'utf8');
251
+ if (/^- \[x\]/m.test(tasksContent)) {
252
+ return { currentState: 'apply-in-progress', inferred: true };
253
+ }
254
+ } catch (_) {
255
+ // Unexpected read error (e.g. permission denied): swallow and fall through
256
+ // to the proposal.md check so inference still produces a useful result.
257
+ }
258
+ }
259
+
260
+ // 6. only proposal.md exists (no design, tasks, specs)
261
+ if (fs.existsSync(path.join(changeDir, 'proposal.md'))) {
262
+ return { currentState: 'proposed', inferred: true };
263
+ }
264
+
265
+ return { currentState: 'unknown', inferred: true };
266
+ }
267
+
186
268
  // --- Subcomandos ---
187
269
 
188
270
  function cmdValidateName(argv) {
@@ -320,7 +402,7 @@ function cmdSetMemory(argv, projectRoot) {
320
402
  const rawName = args._positional[0];
321
403
 
322
404
  if (!rawName) {
323
- console.error('Uso: refacil-sdd-ai sdd set-memory <nombre-cambio> [--last-step <value>] [--stack-detected <value>] [--touched-files <csv>] [--commands-run <value>] [--criteria-run <csv>]');
405
+ console.error('Uso: refacil-sdd-ai sdd set-memory <nombre-cambio> [--last-step <value>] [--stack-detected <value>] [--touched-files <csv>] [--commands-run <value>] [--criteria-run <csv>] [--state <estado>] [--actor <nombre>]');
324
406
  process.exit(1);
325
407
  }
326
408
 
@@ -340,10 +422,30 @@ function cmdSetMemory(argv, projectRoot) {
340
422
  process.exit(1);
341
423
  }
342
424
 
425
+ // Validate --state before touching any file (CR-01: exit 1 without modifying file)
426
+ if (args['state'] !== undefined) {
427
+ const stateValue = args['state'];
428
+ // CR-02: empty string or bare flag (parsed as boolean true) → explicit empty-state error
429
+ if (stateValue === '' || stateValue === true) {
430
+ console.error('set-memory: el estado no puede estar vacío');
431
+ process.exit(1);
432
+ }
433
+ if (!VALID_STATES.includes(stateValue)) {
434
+ console.error(`set-memory: estado inválido '${stateValue}'. Estados válidos: ${VALID_STATES.join(', ')}`);
435
+ process.exit(1);
436
+ }
437
+ }
438
+
439
+ // Validate --actor: pipe character would corrupt the stateHistory pipe format
440
+ if (args['actor'] !== undefined && String(args['actor']).includes('|')) {
441
+ console.error("set-memory: --actor no puede contener el carácter '|'");
442
+ process.exit(1);
443
+ }
444
+
343
445
  // Require at least one field flag
344
- const knownFlags = ['last-step', 'stack-detected', 'touched-files', 'commands-run', 'criteria-run'];
446
+ const knownFlags = ['last-step', 'stack-detected', 'touched-files', 'commands-run', 'criteria-run', 'state', 'actor'];
345
447
  if (!knownFlags.some((f) => args[f] !== undefined)) {
346
- console.error('set-memory: debe especificar al menos un campo (--last-step, --stack-detected, --touched-files, --commands-run, --criteria-run)');
448
+ console.error('set-memory: debe especificar al menos un campo (--last-step, --stack-detected, --touched-files, --commands-run, --criteria-run, --state, --actor)');
347
449
  process.exit(1);
348
450
  }
349
451
 
@@ -370,6 +472,18 @@ function cmdSetMemory(argv, projectRoot) {
370
472
  existing['criteriaRun'] = args['criteria-run'].split(',').map((s) => s.trim()).filter(Boolean);
371
473
  }
372
474
 
475
+ // State tracking (T-01)
476
+ if (args['state'] !== undefined) {
477
+ const actor = args['actor'] !== undefined ? String(args['actor']) : 'cli';
478
+ existing['currentState'] = args['state'];
479
+ const iso = new Date().toISOString();
480
+ const entry = `${args['state']}|${iso}|${actor}`;
481
+ if (!Array.isArray(existing['stateHistory'])) {
482
+ existing['stateHistory'] = [];
483
+ }
484
+ existing['stateHistory'].push(entry);
485
+ }
486
+
373
487
  fs.writeFileSync(memoryPath, serializeMemoryYaml(existing), 'utf8');
374
488
  console.log(`memory.yaml actualizado para '${name}'`);
375
489
  }
@@ -607,12 +721,42 @@ function cmdStatus(argv, projectRoot) {
607
721
  forArchive: reviewPassed && taskStats.total > 0 && taskStats.pending === 0,
608
722
  };
609
723
 
724
+ // Read memory for currentState / lastStep / touchedFiles
725
+ const memoryPath = path.join(changeDir, 'memory.yaml');
726
+ let memory = {};
727
+ if (fs.existsSync(memoryPath)) {
728
+ try {
729
+ memory = parseYaml(fs.readFileSync(memoryPath, 'utf8')) || {};
730
+ } catch (_) {
731
+ memory = {};
732
+ }
733
+ }
734
+
735
+ // Resolve currentState: explicit from memory or inferred
736
+ let currentState = null;
737
+ let stateInferred = false;
738
+ if (memory.currentState) {
739
+ currentState = memory.currentState;
740
+ stateInferred = false;
741
+ } else {
742
+ const inferred = inferCurrentState(changeDir, memory);
743
+ currentState = inferred.currentState;
744
+ stateInferred = inferred.inferred;
745
+ }
746
+
747
+ const lastStep = memory.lastStep || null;
748
+ const touchedFiles = Array.isArray(memory.touchedFiles) ? memory.touchedFiles : [];
749
+
610
750
  const status = {
611
751
  name,
612
752
  artifacts,
613
753
  tasks: taskStats,
614
754
  reviewPassed,
615
755
  ready,
756
+ currentState,
757
+ stateInferred,
758
+ lastStep,
759
+ touchedFiles,
616
760
  };
617
761
 
618
762
  if (wantJson) {
@@ -632,6 +776,11 @@ function cmdStatus(argv, projectRoot) {
632
776
  console.log('');
633
777
  console.log(` Review aprobado: ${reviewPassed ? 'Si' : 'No'}`);
634
778
  console.log('');
779
+ console.log(' Estado:');
780
+ console.log(` currentState: ${currentState}${stateInferred ? ' (inferido)' : ''}`);
781
+ if (lastStep) console.log(` lastStep: ${lastStep}`);
782
+ if (touchedFiles.length > 0) console.log(` touchedFiles: ${touchedFiles.join(', ')}`);
783
+ console.log('');
635
784
  console.log(' Listo para:');
636
785
  console.log(` apply: ${ready.forApply ? 'Si' : 'No'}`);
637
786
  console.log(` archive: ${ready.forArchive ? 'Si' : 'No'}`);
@@ -948,6 +1097,18 @@ function cmdStats(argv, projectRoot) {
948
1097
  const lastStep = memory.lastStep || null;
949
1098
  const criteriaRun = Array.isArray(memory.criteriaRun) ? memory.criteriaRun : [];
950
1099
 
1100
+ // Resolve currentState for stats
1101
+ let statsCurrentState = null;
1102
+ let statsStateInferred = false;
1103
+ if (memory.currentState) {
1104
+ statsCurrentState = memory.currentState;
1105
+ statsStateInferred = false;
1106
+ } else {
1107
+ const inferred = inferCurrentState(changeDir, memory);
1108
+ statsCurrentState = inferred.currentState;
1109
+ statsStateInferred = inferred.inferred;
1110
+ }
1111
+
951
1112
  // --- Read .review-passed ---
952
1113
  const reviewPassedPath = path.join(changeDir, '.review-passed');
953
1114
  let reviewDate = null;
@@ -1026,6 +1187,8 @@ function cmdStats(argv, projectRoot) {
1026
1187
  testCommand: testCommand || null,
1027
1188
  lastStep: lastStep || null,
1028
1189
  criteriaRun,
1190
+ currentState: statsCurrentState,
1191
+ stateInferred: statsStateInferred,
1029
1192
  },
1030
1193
  review: {
1031
1194
  passed: reviewDate !== null,
@@ -1060,6 +1223,7 @@ function cmdStats(argv, projectRoot) {
1060
1223
  if (startDate) console.log(` Inicio estimado: ${startDate.toISOString().slice(0, 10)}`);
1061
1224
  console.log('');
1062
1225
  console.log(' Memory');
1226
+ console.log(` ${pad('currentState', 20)} ${statsCurrentState || noDataLabel}${statsStateInferred ? ' (inferido)' : ''}`);
1063
1227
  console.log(` ${pad('lastStep', 20)} ${lastStep || noDataLabel}`);
1064
1228
  console.log(` ${pad('criteriaRun', 20)} ${criteriaRun.length > 0 ? criteriaRun.join(', ') : noDataLabel}`);
1065
1229
  console.log(` ${pad('testCommand', 20)} ${testCommand || noDataLabel}`);
@@ -1110,6 +1274,8 @@ function sddHelp() {
1110
1274
  [--touched-files <csv>] Archivos modificados (separados por coma)
1111
1275
  [--commands-run <value>] Comando de test ejecutado
1112
1276
  [--criteria-run <csv>] Criterios CA/CR ejecutados (separados por coma)
1277
+ [--state <estado>] Estado del ciclo SDD-AI (proposed, approved, apply-in-progress, applied, tested, verified, reviewed, archived)
1278
+ [--actor <nombre>] Actor que origina el cambio de estado (default: cli)
1113
1279
  sdd get-memory <nombre> Lee memory.yaml del cambio
1114
1280
  [--json] Salida en JSON (por defecto: YAML raw)
1115
1281
  sdd set-review-fails <nombre> Escribe .review-last-fails.json con archivos fallidos
@@ -1124,8 +1290,8 @@ function sddHelp() {
1124
1290
  [--base-branch <branch>] Rama base para nuevos cambios
1125
1291
  [--protected-branches <csv>] Ramas protegidas (separadas por coma)
1126
1292
  [--artifact-language <language>] Idioma para los artefactos SDD generados (english, spanish)
1127
- sdd stats <nombre> [--json] Muestra estadísticas del cambio: memoria, review,
1128
- compact telemetry y CodeGraph en el periodo del cambio
1293
+ sdd stats <nombre> [--json] Muestra estadísticas del cambio: memoria (incluyendo currentState),
1294
+ review, compact telemetry y CodeGraph en el periodo del cambio
1129
1295
  sdd test-scope Resolves scoped test files for the given source files
1130
1296
  --files <csv> Comma-separated source file paths to scope tests for
1131
1297
  [--stack <name>] Stack hint (node, python, go, rust, java, dotnet)
@@ -1214,4 +1380,6 @@ module.exports = {
1214
1380
  cmdWriteConfig,
1215
1381
  cmdStats,
1216
1382
  cmdTestScope,
1383
+ VALID_STATES,
1384
+ inferCurrentState,
1217
1385
  };
package/lib/installer.js CHANGED
@@ -39,6 +39,7 @@ const SKILLS = [
39
39
  'attend',
40
40
  'update',
41
41
  'stats',
42
+ 'status',
42
43
  ];
43
44
 
44
45
  const AGENTS = [
package/lib/kapso.js CHANGED
@@ -66,6 +66,51 @@ function postMessage(creds, bodyText) {
66
66
  });
67
67
  }
68
68
 
69
+ // ─── validation ────────────────────────────────────────────────────────────────
70
+
71
+ // Unfilled template token left in the command, e.g. "<changeName>" or "<repoSlug>".
72
+ const PLACEHOLDER_REGEX = /<[^>]+>/;
73
+
74
+ function isBlankOrPlaceholder(value) {
75
+ if (value === null || value === undefined) return true;
76
+ const s = String(value).trim();
77
+ if (s === '') return true;
78
+ if (PLACEHOLDER_REGEX.test(s)) return true;
79
+ return false;
80
+ }
81
+
82
+ // Returns a list of human-readable problems with the notify payload.
83
+ // Empty list ⇒ the payload is complete enough to send.
84
+ //
85
+ // This is the guard against the autopilot failure mode where the skill fires
86
+ // `kapso notify` once with unfilled <placeholders>/empty flags (sending a junk
87
+ // WhatsApp message) and again with the real values. We reject the incomplete
88
+ // call so it never reaches postMessage().
89
+ function validateNotifyOpts(type, opts) {
90
+ const problems = [];
91
+ const required =
92
+ type === 'success'
93
+ ? ['repo', 'change', 'branch', 'tasks']
94
+ : ['repo', 'change', 'branch', 'phase'];
95
+
96
+ for (const field of required) {
97
+ if (isBlankOrPlaceholder(opts[field])) {
98
+ problems.push(`--${field} faltante, vacío o sin rellenar`);
99
+ }
100
+ }
101
+
102
+ // tasks must look like "done/total" (e.g. "5/5"), not a placeholder or prose.
103
+ if (
104
+ type === 'success' &&
105
+ !isBlankOrPlaceholder(opts.tasks) &&
106
+ !/^\d+\/\d+$/.test(String(opts.tasks).trim())
107
+ ) {
108
+ problems.push(`--tasks con formato inválido (esperado "done/total"): "${opts.tasks}"`);
109
+ }
110
+
111
+ return problems;
112
+ }
113
+
69
114
  // ─── log ─────────────────────────────────────────────────────────────────────
70
115
 
71
116
  function appendLog(msg) {
@@ -150,6 +195,23 @@ function preflight() {
150
195
  // kapso notify failure --repo <slug> --change <name> --branch <branch>
151
196
  // --phase <phase> --last-commit "<commit>" --error "<summary>"
152
197
  async function notify(type, opts) {
198
+ if (type !== 'success' && type !== 'failure') {
199
+ console.error(` Unknown notify type: "${type}". Use "success" or "failure".`);
200
+ process.exit(1);
201
+ }
202
+
203
+ // Reject incomplete/placeholder payloads BEFORE doing anything else so a junk
204
+ // notification is never sent. Throwing surfaces a clear, actionable error to
205
+ // the caller (autopilot) without ever hitting the WhatsApp API.
206
+ const problems = validateNotifyOpts(type, opts);
207
+ if (problems.length > 0) {
208
+ appendLog(`kapso notify ${type} REJECTED (datos inválidos): ${problems.join('; ')}`);
209
+ throw new Error(
210
+ `kapso notify ${type}: datos inválidos o incompletos — ${problems.join('; ')}. ` +
211
+ 'No se envió la notificación. Reintenta con todos los flags requeridos rellenados con valores reales.',
212
+ );
213
+ }
214
+
153
215
  const creds = readCredentials();
154
216
  if (!creds) {
155
217
  appendLog('kapso notify skipped: no credentials');
@@ -218,9 +280,6 @@ async function notify(type, opts) {
218
280
  '',
219
281
  'El árbol de trabajo quedó en su estado actual para revisión.',
220
282
  ].join('\n');
221
- } else {
222
- console.error(` Unknown notify type: "${type}". Use "success" or "failure".`);
223
- process.exit(1);
224
283
  }
225
284
 
226
285
  try {
@@ -238,4 +297,12 @@ async function notify(type, opts) {
238
297
  }
239
298
  }
240
299
 
241
- module.exports = { setup, preflight, notify, readCredentials, PHONE_REGEX };
300
+ module.exports = {
301
+ setup,
302
+ preflight,
303
+ notify,
304
+ readCredentials,
305
+ validateNotifyOpts,
306
+ isBlankOrPlaceholder,
307
+ PHONE_REGEX,
308
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "refacil-sdd-ai",
3
- "version": "5.3.0",
3
+ "version": "5.3.1",
4
4
  "description": "SDD-AI: Specification-Driven Development with AI — development methodology using AI with Claude Code, Cursor, OpenCode and Codex",
5
5
  "bin": {
6
6
  "refacil-sdd-ai": "./bin/cli.js"
@@ -40,7 +40,7 @@
40
40
  },
41
41
  "scripts": {
42
42
  "postinstall": "node ./bin/postinstall.js",
43
- "test": "node --test --test-concurrency=1 test/global-paths.test.js test/opencode-global-path-windows.test.js test/opencode-migrate.test.js test/opencode-clean-global.test.js test/skills-parity.test.js test/multi-ide-parity-pass2.test.js test/repo-ide-sync.test.js test/global-install.test.js test/hooks.test.js test/installer.test.js test/ignore-files.test.js test/methodology-migration-pending.test.js test/sdd.test.js test/test-scope.test.js test/config.test.js test/refactor-integrar-openspec-nativo.test.js test/refactor-rutas-refacil-sdd.test.js test/refactor-agents-english.test.js test/remove-openspec-legacy.test.js test/find-project-root.test.js test/project-root.test.js test/opencode-installer.test.js test/opencode-plugin.test.js test/check-review.test.js test/toml-converter.test.js test/testing-policy-sync.test.js test/session-repo-sync.test.js test/compact-guidance.test.js test/codegraph.test.js test/codegraph-telemetry.test.js test/compact-codegraph.test.js test/autopilot-skill-install.test.js test/read-spec-md-parser.test.js test/read-spec.test.js test/spec-sync.test.js test/kapso-init-session-removed.test.js test/kapso-preflight.test.js test/autopilot-ready-message.test.js test/stats-observability.test.js test/bus-from-env.test.js test/imp-scoped-test-execution.test.js test/broker-port-env.test.js"
43
+ "test": "node --test --test-concurrency=1 test/global-paths.test.js test/opencode-global-path-windows.test.js test/opencode-migrate.test.js test/opencode-clean-global.test.js test/skills-parity.test.js test/multi-ide-parity-pass2.test.js test/repo-ide-sync.test.js test/global-install.test.js test/hooks.test.js test/installer.test.js test/ignore-files.test.js test/methodology-migration-pending.test.js test/sdd.test.js test/test-scope.test.js test/config.test.js test/refactor-integrar-openspec-nativo.test.js test/refactor-rutas-refacil-sdd.test.js test/refactor-agents-english.test.js test/remove-openspec-legacy.test.js test/find-project-root.test.js test/project-root.test.js test/opencode-installer.test.js test/opencode-plugin.test.js test/check-review.test.js test/toml-converter.test.js test/testing-policy-sync.test.js test/session-repo-sync.test.js test/compact-guidance.test.js test/codegraph.test.js test/codegraph-telemetry.test.js test/compact-codegraph.test.js test/autopilot-skill-install.test.js test/read-spec-md-parser.test.js test/read-spec.test.js test/spec-sync.test.js test/kapso-init-session-removed.test.js test/kapso-preflight.test.js test/autopilot-ready-message.test.js test/stats-observability.test.js test/bus-from-env.test.js test/imp-scoped-test-execution.test.js test/broker-port-env.test.js test/change-state-tracking.test.js"
44
44
  },
45
45
  "dependencies": {
46
46
  "smol-toml": "^1.6.1",
@@ -113,6 +113,14 @@ specsNote: <"specs.md" | "specs/**/*.md" | "both — report contradictions in is
113
113
 
114
114
  ### Step 2: Delegate to the refacil-implementer sub-agent
115
115
 
116
+ Before delegating, record that implementation has started:
117
+
118
+ ```bash
119
+ refacil-sdd-ai sdd set-memory <changeName> --state apply-in-progress --actor apply-skill
120
+ ```
121
+
122
+ If the command fails, continue silently — it must not block the flow.
123
+
116
124
  Invoke the `refacil-implementer` sub-agent passing it the BRIEFING from the previous step plus:
117
125
  - `changeName` (redundant with the briefing, but the guardrail needs it)
118
126
  - If the user requested detailed mode, indicate it. Default: concise.
@@ -130,7 +138,9 @@ Run:
130
138
  ```bash
131
139
  refacil-sdd-ai sdd set-memory <changeName> \
132
140
  --last-step apply \
133
- --touched-files "<comma-separated list of modified files>"
141
+ --touched-files "<comma-separated list of modified files>" \
142
+ --state applied \
143
+ --actor apply-skill
134
144
  ```
135
145
 
136
146
  This command merges into memory.yaml at the repo root using `findProjectRoot()` — no manual path construction needed.
@@ -174,10 +174,18 @@ Minimal bug fixes only contain `summary.md` (and optionally `.review-passed`) an
174
174
  ```
175
175
  - If `review.yaml` already exists, update only the fields that changed without removing others.
176
176
 
177
- 3. **Run the CLI archive**: `refacil-sdd-ai sdd archive <changeName>` sync-spec, preserves `memory.yaml` if present, then moves the complete change directory to `refacil-sdd/changes/archive/<date>-<changeName>/`.
178
- 4. Verify the command completed successfully (exit 0) and the original folder no longer exists.
177
+ 3. **Record archived state** — run this command **while the change directory is still at its original path** (`refacil-sdd/changes/<changeName>/`), before the CLI move in step 4. After the move that path no longer exists and `set-memory` would fail to locate the change:
179
178
 
180
- 5. Continue to **Step 3**.
179
+ ```bash
180
+ refacil-sdd-ai sdd set-memory <changeName> --state archived --actor archive-skill
181
+ ```
182
+
183
+ If the command fails, continue silently — it must not block the flow.
184
+
185
+ 4. **Run the CLI archive**: `refacil-sdd-ai sdd archive <changeName>` — sync-spec, preserves `memory.yaml` if present, then moves the complete change directory to `refacil-sdd/changes/archive/<date>-<changeName>/`.
186
+ 5. Verify the command completed successfully (exit 0) and the original folder no longer exists.
187
+
188
+ 6. Continue to **Step 3**.
181
189
 
182
190
  The goal is for `refacil-sdd/specs/` to document how the system works TODAY.
183
191
 
@@ -354,9 +354,10 @@ When any phase fails:
354
354
  - `errorSummary`: 1-3 line summary.
355
355
  - `branchAtFailure`: `git branch --show-current`.
356
356
  - `lastCommit`: `git log -1 --oneline`.
357
- 4. Delete the autopilot marker: `rm -f "refacil-sdd/.autopilot-active"`.
358
- 5. Send the failure notification via Kapso only if `kapsoEnabled = true` (Step 7, failure template).
359
- 6. Leave the working tree as-is so the user can inspect when they return. Normal recovery must never use destructive reset commands; preserve local edits and report the evidence needed for manual repair.
357
+ 4. Send the failure notification via Kapso only if `kapsoEnabled = true` (Step 7, failure template).
358
+ 5. Leave the working tree as-is so the user can inspect when they return. Normal recovery must never use destructive reset commands; preserve local edits and report the evidence needed for manual repair.
359
+
360
+ > **Note**: do NOT delete `refacil-sdd/.autopilot-active` here. Step 8 deletes the marker unconditionally at the end of the cycle — this keeps the marker alive during Step 7 in both success and failure paths, so the CLI can compute the exact duration from `startedAt`.
360
361
 
361
362
  ### Step 7: Notify via Kapso
362
363
 
@@ -364,37 +365,65 @@ When any phase fails:
364
365
 
365
366
  All notification logic — credentials, message formatting, HTTP call, error logging — is handled by the CLI. Do NOT construct curl calls or read the env file directly.
366
367
 
368
+ **Send the notification exactly ONCE, with every flag resolved to a real value — never a placeholder.** Before invoking `kapso notify`, confirm that `repoSlug`, `changeName`, `branchAtStart`/`branchAtFailure`, `tasks.done`/`tasks.total` (and `phase` on failure) are all filled with concrete values captured in Steps 0–6 — there must be no literal `<...>` token left in the command. Do NOT fire a first "probe" call and then a corrected one: a single notification per run. The CLI **rejects** any `notify` whose required flags are missing, empty, or still contain a `<placeholder>` (it exits non-zero without sending, and prints which flags are wrong). If you see that rejection, it means a value was not resolved — fill it in from the captured state and invoke once more; do not let an empty notification go out.
369
+
367
370
  **Success notification**:
368
371
 
372
+ Required flags (validated by `validateNotifyOpts`; the CLI rejects the call if any is missing or contains a placeholder):
373
+ - `--repo` — repository slug (e.g. `refacil-ia`)
374
+ - `--change` — change name (e.g. `imp-session-timeout-redis`)
375
+ - `--branch` — branch at start (e.g. `feature/imp-session-timeout-redis`)
376
+ - `--tasks` — tasks in `done/total` format (e.g. `5/5`). Must match `^\d+/\d+$`.
377
+
378
+ Optional flags (omit if not applicable):
379
+ - `--pr` — PR link, empty string, or `"skipped"` (see rules below)
380
+ - `--apply`, `--test`, `--review` — improvement counters (integer, default `0`)
381
+ - `--warnings` — top 3 warning strings joined by `|` (omit or pass `""` if none)
382
+
383
+ > **Duration**: the CLI computes the run duration automatically from the `startedAt` field written to `.autopilot-active` in Step 0.7. You do **not** need to pass `--duration` — omit it and the CLI reads `startedAt` from the marker. Only pass `--duration <minutes>` as a manual fallback if the marker was unavailable for some reason (this should never happen with the corrected Step 6 that no longer deletes the marker before Step 7).
384
+
369
385
  The `--pr` flag value depends on `includeUpCode`:
370
386
  - `includeUpCode = true` and push succeeded with PR → `--pr "<prLink>"`
371
387
  - `includeUpCode = true` and push succeeded without PR (`createPR = false`) → `--pr ""` (empty string)
372
388
  - `includeUpCode = false` → `--pr "skipped"`
373
389
 
390
+ Example (all required flags filled with real values):
391
+
374
392
  ```bash
375
393
  refacil-sdd-ai kapso notify success \
376
- --repo "<repoSlug>" \
377
- --change "<changeName>" \
378
- --branch "<branchAtStart>" \
379
- --tasks "<tasks.done>/<tasks.total>" \
380
- --duration "<minutes>" \
381
- --pr "<prLink | empty string | skipped>" \
382
- --apply "<improvementsApplied.apply>" \
383
- --test "<improvementsApplied.test>" \
384
- --review "<improvementsApplied.review>" \
385
- --warnings "<top 3 warnings joined by | or empty>"
394
+ --repo "refacil-ia" \
395
+ --change "imp-session-timeout-redis" \
396
+ --branch "feature/imp-session-timeout-redis" \
397
+ --tasks "5/5" \
398
+ --pr "https://github.com/org/refacil-ia/pull/42" \
399
+ --apply "1" \
400
+ --test "0" \
401
+ --review "0" \
402
+ --warnings ""
386
403
  ```
387
404
 
388
405
  **Failure notification**:
389
406
 
407
+ Required flags:
408
+ - `--repo` — repository slug
409
+ - `--change` — change name
410
+ - `--branch` — branch at the moment of failure (`branchAtFailure`)
411
+ - `--phase` — which phase failed (e.g. `verify`, `test`, `apply`, `review`, `archive`, `up-code`)
412
+
413
+ Optional flags:
414
+ - `--last-commit` — last commit hash + message from `git log -1 --oneline`
415
+ - `--error` — 1-3 line error summary
416
+
417
+ Example (all required flags filled with real values):
418
+
390
419
  ```bash
391
420
  refacil-sdd-ai kapso notify failure \
392
- --repo "<repoSlug>" \
393
- --change "<changeName>" \
394
- --branch "<branchAtFailure>" \
395
- --phase "<phase>" \
396
- --last-commit "<lastCommit>" \
397
- --error "<errorSummary>"
421
+ --repo "refacil-ia" \
422
+ --change "imp-session-timeout-redis" \
423
+ --branch "feature/imp-session-timeout-redis" \
424
+ --phase "verify" \
425
+ --last-commit "a1b2c3d feat: add session timeout to Redis store" \
426
+ --error "verify: 2 unresolved CRITICAL findings after 2 autofix rounds"
398
427
  ```
399
428
 
400
429
  The CLI reads credentials from `~/.refacil-sdd-ai/kapso.env` internally. If Kapso returns an error, it logs to `~/.refacil-sdd-ai/autopilot.log` and exits without crashing — the SDD work is intact.
@@ -35,10 +35,13 @@ You are a **brief** guide: you choose the next command; the detail of each flow
35
35
  11. Autonomous pipeline (after approved propose) → `/refacil:autopilot` — same chain as option 1 path B; use when you want autonomous execution ("autopilot", "modo autónomo", "termina solo el flujo"). During pre-flight you define whether up-code (push + PR) is included — the cycle adapts and can end at archive or continue with up-code.
36
36
  12. Listen to specs with voice (TTS) → `/refacil:read-spec` — on-device browser playback; post-propose option B in propose, or on-demand for active changes / archived specs (`refacil-sdd-ai read-spec --change <name>`)
37
37
  13. Change progress and metrics → `/refacil:stats` — task completion, review gate, test commands from `memory.yaml` (`refacil-sdd-ai sdd stats <changeName>`)
38
+ 14. Current phase and resume → `/refacil:status` — which phase of the SDD-AI cycle a change is in and the exact command to resume it (`refacil-sdd-ai sdd status <changeName>`)
38
39
 
39
40
  > **Note**: `up-code` verifies `.review-passed` before push; see `METHODOLOGY-CONTRACT.md §5-6` for details.
40
41
 
41
- > **Skill parity**: 21 user-invocable skills (`user-invocable: true`, excluding internal `prereqs`) must appear in `SKILLS[]`, this menu, and the README "Available IDE Skills" table — including `read-spec` and `stats`.
42
+ > **Skill parity**: 22 user-invocable skills (`user-invocable: true`, excluding internal `prereqs`) must appear in `SKILLS[]`, this menu, and the README "Available IDE Skills" table — including `read-spec`, `stats`, and `status`.
43
+
44
+ > **stats vs status**: `/refacil:stats` shows **telemetry** (token savings, compact rewrites, CodeGraph calls, review history). `/refacil:status` shows **navigation** (current cycle phase, next skill to run, state history). Use status to navigate the flow; use stats for observability data.
42
45
 
43
46
  ### After `/refacil:propose` is approved
44
47
 
@@ -91,6 +94,7 @@ Full detail in the refacil-sdd-ai README (section `refacil-bus`).
91
94
  - Option 11 (Autonomous pipeline) → `/refacil:autopilot`
92
95
  - Option 12 (Listen to specs) → `/refacil:read-spec`
93
96
  - Option 13 (Change progress) → `/refacil:stats`
97
+ - Option 14 (Current phase and resume) → `/refacil:status`
94
98
  - Post-propose context with a single clear next step: if artifacts are approved and the user wants hands-off → `/refacil:autopilot`; if they want to hear specs first → `/refacil:read-spec`; if they want step-by-step → `/refacil:apply`.
95
99
  - If the intent does not map exactly to an option, do NOT invoke — list numbered options to the user and ask for explicit selection.
96
100
 
@@ -132,6 +132,14 @@ If the user requests limited adjustments (change a criterion, fix a path, adjust
132
132
 
133
133
  ### Step 4: Next step
134
134
 
135
+ **Before presenting the menu**, record that the proposal has been approved by running:
136
+
137
+ ```bash
138
+ refacil-sdd-ai sdd set-memory <changeName> --state approved --actor propose-skill
139
+ ```
140
+
141
+ If the command fails, continue silently — it must not block the flow.
142
+
135
143
  **Always present this menu as a new message** immediately after Step 3 confirms approval. Never skip it or reuse the "OK" from Step 3 as a selection here — that "OK" means "the artifacts are approved", not "choose option A".
136
144
 
137
145
  Before presenting the menu, run `refacil-sdd-ai kapso preflight` (exits 0, prints JSON) and read `kapsoEnabled` from the output. If `kapsoEnabled = true`, append the WhatsApp note to option B. If `kapsoEnabled = false`, omit it.
@@ -167,6 +167,14 @@ Where the values are extracted from the sub-agent's `refacil-review-result` bloc
167
167
  - `changeName` is null.
168
168
  - The sub-agent returned `SCOPE_ERROR`.
169
169
 
170
+ After writing `.review-passed` with an approved verdict, record the state:
171
+
172
+ ```bash
173
+ refacil-sdd-ai sdd set-memory <changeName> --state reviewed --actor review-skill
174
+ ```
175
+
176
+ If the command fails or `changeName` is null, continue silently — it must not block the flow.
177
+
170
178
  ### Step 3.5: Offer to apply corrections (only if REQUIERE CORRECCIONES)
171
179
 
172
180
  If `verdict` is `REQUIERE CORRECCIONES`:
@@ -51,7 +51,9 @@ Parse the JSON block returned by the CLI. Expected fields:
51
51
  "memory": {
52
52
  "testCommand": "<command or null>",
53
53
  "lastStep": "<step or null>",
54
- "criteriaRun": ["CA-01", "CR-01", ...]
54
+ "criteriaRun": ["CA-01", "CR-01", ...],
55
+ "currentState": "<state or null>",
56
+ "stateInferred": true | false
55
57
  },
56
58
  "review": {
57
59
  "passed": true | false,
@@ -86,6 +88,7 @@ Present the data in a readable format. If `isArchived` is `true`, add `[archivad
86
88
 
87
89
  Phase progress
88
90
  Started: <startDate or "unknown">
91
+ Current state: <currentState or "unknown"> <(inferred) if stateInferred is true>
89
92
  Last step: <lastStep or "not recorded">
90
93
  Criteria: <criteriaRun list or "none">
91
94
 
@@ -0,0 +1,116 @@
1
+ ---
2
+ name: refacil:status
3
+ description: Show which phase of the SDD-AI cycle a change is in and the exact command to resume it — use for "where am I?", "how is it going?", "status of the change", "resume", "where did I leave off". Distinct from /refacil:stats (which shows telemetry, token savings, and review metrics). Use /refacil:status to navigate the flow; use /refacil:stats for observability data.
4
+ user-invocable: true
5
+ ---
6
+
7
+ # refacil:status — Current Phase and Resume
8
+
9
+ This skill answers: **"In which phase of the SDD-AI cycle is this change, and what command do I run next?"**
10
+
11
+ It is distinct from `/refacil:stats`:
12
+ - `/refacil:status` — **navigation**: current phase, next skill to run, resume a paused flow.
13
+ - `/refacil:stats` — **telemetry**: token savings, compact rewrites, CodeGraph calls, review history.
14
+
15
+ **Prerequisites**: `sdd` profile from `refacil-prereqs/SKILL.md`.
16
+
17
+ ## Activation phrases
18
+
19
+ Invoke this skill for expressions like: "¿en qué va?", "¿cómo voy?", "status del cambio", "retomar", "dónde quedé", "where did I leave off?", "resume", "what's the current state?", "what's next?".
20
+
21
+ ## Next-skill mapping
22
+
23
+ The following table maps each state to the recommended next skill in the **normal flow** and in the **bug fix flow** (`fix-*`):
24
+
25
+ | State | Normal flow next skill | Bug fix (fix-*) next skill |
26
+ |-------------------|--------------------------|---------------------------|
27
+ | `proposed` | `/refacil:propose` (approve or adjust) | — |
28
+ | `approved` | `/refacil:apply` | `/refacil:apply` |
29
+ | `apply-in-progress` | `/refacil:apply` | `/refacil:apply` |
30
+ | `applied` | `/refacil:test` | `/refacil:test` |
31
+ | `tested` | `/refacil:verify` | `/refacil:review` (bugs omit verify) |
32
+ | `verified` | `/refacil:review` | — (n/a: bugs never reach verified) |
33
+ | `reviewed` | `/refacil:archive` | `/refacil:archive` |
34
+ | `archived` | `/refacil:up-code` | `/refacil:up-code` |
35
+ | `unknown` | Run `/refacil:propose` to create artifacts | — |
36
+
37
+ ## Flow
38
+
39
+ ### Without argument — overview of all active changes
40
+
41
+ 1. Run `refacil-sdd-ai sdd list --json` to get all active changes.
42
+
43
+ 2. If the list is empty, inform the user:
44
+ ```
45
+ No active changes found. Run /refacil:propose to start a new change.
46
+ ```
47
+ Stop.
48
+
49
+ 3. For each active change, run `refacil-sdd-ai sdd status <changeName> --json` and collect:
50
+ - `currentState` and `stateInferred`
51
+ - `tasks.done` / `tasks.total`
52
+ - `reviewPassed`
53
+
54
+ 4. Present a summary table:
55
+
56
+ ```
57
+ === Active changes ===
58
+
59
+ Change Phase Next command
60
+ ─────────────────────────────────────────────────────────────────
61
+ <changeName> <currentState> (inferred?) <next skill>
62
+ ...
63
+ ```
64
+
65
+ For each row, derive the next skill from the **next-skill mapping** above (use the normal flow unless the change name starts with `fix-`).
66
+
67
+ State history is shown only in the detail view — pass a change name as argument to see the full transition log.
68
+
69
+ ### With argument — detail for one change
70
+
71
+ 1. Use `$ARGUMENTS` as `changeName`.
72
+
73
+ 2. Run `refacil-sdd-ai sdd status <changeName> --json` and parse the output.
74
+
75
+ 3. If the command exits 1 (change not found), inform the user and stop.
76
+
77
+ 4. Resolve `currentState`:
78
+ - Use `status.currentState` from the JSON directly.
79
+ - If `stateInferred` is `true`, note it as `(inferred from artifacts)`.
80
+
81
+ 5. Parse `stateHistory` from memory (if available):
82
+ - Run `refacil-sdd-ai sdd get-memory <changeName> --json`.
83
+ - Read the `stateHistory` array. Each entry is a pipe-separated string `"<state>|<ISO>|<actor>"`.
84
+ - Parse each entry with `entry.split('|')` → `[state, iso, actor]`.
85
+ - Show the last 5 entries (most recent last) as a compact history.
86
+
87
+ 6. Determine the next skill from the **next-skill mapping** table above.
88
+
89
+ 7. Present:
90
+
91
+ ```
92
+ === Status: <changeName> ===
93
+
94
+ Current phase: <currentState> <(inferred from artifacts)?>
95
+ Last step: <lastStep or "not recorded">
96
+ Touched files: <touchedFiles list or "none">
97
+
98
+ State history (last 5):
99
+ <date> — <state> (by <actor>)
100
+ ...
101
+
102
+ Next command: <next skill>
103
+ ```
104
+
105
+ If the change is in `archived` state, add:
106
+ ```
107
+ This change is fully archived. Run /refacil:up-code to push and create the PR.
108
+ ```
109
+
110
+ ## Rules
111
+
112
+ - **Read-only**: this skill does not modify any file, branch, or memory.
113
+ - **Graceful degradation**: if `stateHistory` is absent or `get-memory` fails, show the current state without history. Do not error.
114
+ - **Empty list behavior**: if `sdd list --json` returns `[]`, inform the user and stop without error.
115
+ - **Bug fix detection**: if `changeName` starts with `fix-`, use the bug fix flow column in the next-skill table. The bug flow skips `/refacil:verify`: a `tested` fix goes straight to `/refacil:review`, and a fix never reaches the `verified` state.
116
+ - **Flow continuity**: if the user confirms affirmatively ("yes", "ok", "go", "continue", etc.) after the status display and there is a single unambiguous next skill, immediately execute that skill. (See `METHODOLOGY-CONTRACT.md §5`.)
@@ -124,7 +124,9 @@ Run:
124
124
  refacil-sdd-ai sdd set-memory <changeName> \
125
125
  --last-step test \
126
126
  --commands-run "<test command used>" \
127
- --criteria-run "<comma-separated criteria IDs that were run>"
127
+ --criteria-run "<comma-separated criteria IDs that were run>" \
128
+ --state tested \
129
+ --actor test-skill
128
130
  ```
129
131
 
130
132
  If `stackDetected` is available, add `--stack-detected "<stack>"` to the command as well.
@@ -148,6 +148,14 @@ Parse the ` ```refacil-verify-result ` block from the sub-agent.
148
148
 
149
149
  #### If `result` is APPROVED:
150
150
 
151
+ Record that verification completed successfully:
152
+
153
+ ```bash
154
+ refacil-sdd-ai sdd set-memory <changeName> --state verified --actor verify-skill
155
+ ```
156
+
157
+ If the command fails, continue silently — it must not block the flow.
158
+
151
159
  - `autopilotMode = false` (normal): ask the user:
152
160
  ```
153
161
  RESULT: APPROVED