pumuki 6.3.39 → 6.3.40

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 (63) hide show
  1. package/README.md +21 -12
  2. package/VERSION +1 -1
  3. package/core/gate/evaluateRules.test.ts +40 -0
  4. package/core/gate/evaluateRules.ts +7 -1
  5. package/core/rules/Consequence.ts +1 -0
  6. package/docs/CONFIGURATION.md +50 -0
  7. package/docs/INSTALLATION.md +38 -11
  8. package/docs/MCP_SERVERS.md +1 -1
  9. package/docs/README.md +1 -0
  10. package/docs/RELEASE_NOTES.md +44 -0
  11. package/docs/USAGE.md +191 -9
  12. package/docs/registro-maestro-de-seguimiento.md +2 -2
  13. package/docs/seguimiento-activo-pumuki-saas-supermercados.md +1592 -1
  14. package/docs/validation/README.md +2 -1
  15. package/docs/validation/ast-intelligence-roadmap.md +96 -0
  16. package/integrations/config/skillsCustomRules.ts +14 -0
  17. package/integrations/config/skillsDetectorRegistry.ts +11 -1
  18. package/integrations/config/skillsLock.ts +30 -0
  19. package/integrations/config/skillsMarkdownRules.ts +14 -3
  20. package/integrations/config/skillsRuleSet.ts +25 -3
  21. package/integrations/evidence/readEvidence.test.ts +3 -2
  22. package/integrations/evidence/readEvidence.ts +14 -4
  23. package/integrations/evidence/repoState.ts +10 -2
  24. package/integrations/evidence/schema.test.ts +3 -2
  25. package/integrations/evidence/schema.ts +3 -0
  26. package/integrations/evidence/writeEvidence.test.ts +3 -2
  27. package/integrations/gate/evaluateAiGate.ts +511 -2
  28. package/integrations/git/GitService.ts +5 -1
  29. package/integrations/git/astIntelligenceDualValidation.ts +275 -0
  30. package/integrations/git/gitAtomicity.ts +42 -9
  31. package/integrations/git/resolveGitRefs.ts +37 -0
  32. package/integrations/git/runPlatformGate.ts +228 -1
  33. package/integrations/git/runPlatformGateEvaluation.ts +4 -0
  34. package/integrations/git/stageRunners.ts +116 -2
  35. package/integrations/lifecycle/cli.ts +759 -22
  36. package/integrations/lifecycle/doctor.ts +62 -0
  37. package/integrations/lifecycle/index.ts +1 -0
  38. package/integrations/lifecycle/packageInfo.ts +25 -3
  39. package/integrations/lifecycle/policyReconcile.ts +304 -0
  40. package/integrations/lifecycle/preWriteAutomation.ts +42 -2
  41. package/integrations/lifecycle/watch.ts +365 -0
  42. package/integrations/mcp/aiGateCheck.ts +59 -2
  43. package/integrations/mcp/autoExecuteAiStart.ts +25 -1
  44. package/integrations/mcp/preFlightCheck.ts +13 -0
  45. package/integrations/sdd/evidenceScaffold.ts +223 -0
  46. package/integrations/sdd/index.ts +2 -0
  47. package/integrations/sdd/stateSync.ts +400 -0
  48. package/integrations/sdd/syncDocs.ts +97 -2
  49. package/package.json +4 -1
  50. package/scripts/backlog-action-reasons-lib.ts +38 -0
  51. package/scripts/backlog-id-issue-map-lib.ts +69 -0
  52. package/scripts/backlog-json-contract-lib.ts +3 -0
  53. package/scripts/framework-menu-consumer-preflight-lib.ts +6 -0
  54. package/scripts/package-install-smoke-command-resolution-lib.ts +64 -0
  55. package/scripts/package-install-smoke-consumer-npm-lib.ts +43 -0
  56. package/scripts/package-install-smoke-consumer-repo-setup-lib.ts +2 -0
  57. package/scripts/package-install-smoke-execution-steps-lib.ts +27 -9
  58. package/scripts/package-install-smoke-lifecycle-lib.ts +15 -4
  59. package/scripts/package-install-smoke-workspace-factory-lib.ts +4 -1
  60. package/scripts/reconcile-consumer-backlog-issues-lib.ts +651 -0
  61. package/scripts/reconcile-consumer-backlog-issues.ts +348 -0
  62. package/scripts/watch-consumer-backlog-lib.ts +465 -0
  63. package/scripts/watch-consumer-backlog.ts +326 -0
@@ -32,8 +32,12 @@ import {
32
32
  readSddStatus,
33
33
  refreshSddSession,
34
34
  runSddAutoSync,
35
+ runSddEvidenceScaffold,
35
36
  runSddLearn,
37
+ runSddStateSync,
36
38
  runSddSyncDocs,
39
+ type SddEvidenceScaffoldTestStatus,
40
+ type SddStateSyncStatus,
37
41
  type SddStage,
38
42
  } from '../sdd';
39
43
  import { evaluateAiGate } from '../gate/evaluateAiGate';
@@ -46,6 +50,12 @@ import {
46
50
  buildPreWriteAutomationTrace,
47
51
  type PreWriteAutomationTrace,
48
52
  } from './preWriteAutomation';
53
+ import {
54
+ runLifecycleWatch,
55
+ type LifecycleWatchScope,
56
+ type LifecycleWatchSeverityThreshold,
57
+ type LifecycleWatchStage,
58
+ } from './watch';
49
59
  import { buildLocalHotspotsReport, type LocalHotspotsReport } from './analyticsHotspots';
50
60
  import { resolveHotspotsSaasIngestionAuditPath } from './saasIngestionAudit';
51
61
  import { readHotspotsSaasIngestionPayload } from './saasIngestionContract';
@@ -58,23 +68,36 @@ import {
58
68
  collectRemoteCiDiagnostics,
59
69
  type RemoteCiDiagnostics,
60
70
  } from './remoteCiDiagnostics';
71
+ import { runPolicyReconcile } from './policyReconcile';
61
72
 
62
73
  type LifecycleCommand =
74
+ | 'bootstrap'
63
75
  | 'install'
64
76
  | 'uninstall'
65
77
  | 'remove'
66
78
  | 'update'
67
79
  | 'doctor'
68
80
  | 'status'
81
+ | 'watch'
69
82
  | 'loop'
70
83
  | 'sdd'
71
84
  | 'adapter'
72
- | 'analytics';
85
+ | 'analytics'
86
+ | 'policy';
73
87
 
74
- type SddCommand = 'status' | 'validate' | 'session' | 'sync-docs' | 'learn' | 'auto-sync';
88
+ type SddCommand =
89
+ | 'status'
90
+ | 'validate'
91
+ | 'session'
92
+ | 'sync-docs'
93
+ | 'learn'
94
+ | 'auto-sync'
95
+ | 'evidence'
96
+ | 'state-sync';
75
97
  type LoopCommand = 'run' | 'status' | 'stop' | 'resume' | 'list' | 'export';
76
98
  type AnalyticsCommand = 'hotspots';
77
99
  type AnalyticsHotspotsCommand = 'report' | 'diagnose';
100
+ type PolicyCommand = 'reconcile';
78
101
 
79
102
  type SddSessionAction = 'open' | 'refresh' | 'close';
80
103
 
@@ -83,6 +106,10 @@ type ParsedArgs = {
83
106
  purgeArtifacts: boolean;
84
107
  updateSpec?: string;
85
108
  json: boolean;
109
+ bootstrapEnterprise?: boolean;
110
+ bootstrapAgent?: AdapterAgent;
111
+ installWithMcp?: boolean;
112
+ installMcpAgent?: AdapterAgent;
86
113
  remoteChecks?: boolean;
87
114
  doctorDeep?: boolean;
88
115
  sddCommand?: SddCommand;
@@ -99,14 +126,36 @@ type ParsedArgs = {
99
126
  sddSyncDocsChange?: string;
100
127
  sddSyncDocsStage?: SddStage;
101
128
  sddSyncDocsTask?: string;
129
+ sddSyncDocsFromEvidence?: string;
102
130
  sddLearnDryRun?: boolean;
103
131
  sddLearnChange?: string;
104
132
  sddLearnStage?: SddStage;
105
133
  sddLearnTask?: string;
134
+ sddLearnFromEvidence?: string;
106
135
  sddAutoSyncDryRun?: boolean;
107
136
  sddAutoSyncChange?: string;
108
137
  sddAutoSyncStage?: SddStage;
109
138
  sddAutoSyncTask?: string;
139
+ sddAutoSyncFromEvidence?: string;
140
+ sddEvidenceDryRun?: boolean;
141
+ sddEvidenceScenarioId?: string;
142
+ sddEvidenceTestCommand?: string;
143
+ sddEvidenceTestStatus?: SddEvidenceScaffoldTestStatus;
144
+ sddEvidenceTestOutput?: string;
145
+ sddEvidenceFromEvidence?: string;
146
+ sddStateSyncDryRun?: boolean;
147
+ sddStateSyncScenarioId?: string;
148
+ sddStateSyncStatus?: SddStateSyncStatus;
149
+ sddStateSyncFromEvidence?: string;
150
+ sddStateSyncBoardPath?: string;
151
+ sddStateSyncForce?: boolean;
152
+ watchStage?: LifecycleWatchStage;
153
+ watchScope?: LifecycleWatchScope;
154
+ watchIntervalMs?: number;
155
+ watchNotifyCooldownMs?: number;
156
+ watchSeverityThreshold?: LifecycleWatchSeverityThreshold;
157
+ watchNotifyEnabled?: boolean;
158
+ watchIterations?: number;
110
159
  adapterCommand?: 'install';
111
160
  adapterAgent?: AdapterAgent;
112
161
  adapterDryRun?: boolean;
@@ -116,16 +165,20 @@ type ParsedArgs = {
116
165
  analyticsSinceDays?: number;
117
166
  analyticsJsonOutputPath?: string;
118
167
  analyticsMarkdownOutputPath?: string;
168
+ policyCommand?: PolicyCommand;
169
+ policyStrict?: boolean;
119
170
  };
120
171
 
121
172
  const HELP_TEXT = `
122
173
  Pumuki lifecycle commands:
123
- pumuki install
174
+ pumuki bootstrap [--enterprise] [--agent=<name>] [--json]
175
+ pumuki install [--with-mcp] [--agent=<name>]
124
176
  pumuki uninstall [--purge-artifacts]
125
177
  pumuki remove
126
178
  pumuki update [--latest|--spec=<package-spec>]
127
179
  pumuki doctor [--remote-checks] [--deep] [--json]
128
180
  pumuki status [--json] [--remote-checks]
181
+ pumuki watch [--stage=PRE_COMMIT|PRE_PUSH|CI] [--scope=workingTree|staged|repoAndStaged|repo] [--severity=critical|high|medium|low] [--interval-ms=<n>] [--notify-cooldown-ms=<n>] [--no-notify] [--once|--iterations=<n>] [--json]
129
182
  pumuki loop run --objective=<text> [--max-attempts=<n>] [--json]
130
183
  pumuki loop status --session=<session-id> [--json]
131
184
  pumuki loop stop --session=<session-id> [--json]
@@ -135,14 +188,19 @@ Pumuki lifecycle commands:
135
188
  pumuki adapter install --agent=<name> [--dry-run] [--json]
136
189
  pumuki analytics hotspots report [--top=<n>] [--since-days=<n>] [--json] [--output-json=<path>] [--output-markdown=<path>]
137
190
  pumuki analytics hotspots diagnose [--json]
191
+ pumuki policy reconcile [--strict] [--json]
138
192
  pumuki sdd status [--json]
139
193
  pumuki sdd validate [--stage=PRE_WRITE|PRE_COMMIT|PRE_PUSH|CI] [--json]
140
194
  pumuki sdd session --open --change=<change-id> [--ttl-minutes=<n>] [--json]
141
195
  pumuki sdd session --refresh [--ttl-minutes=<n>] [--json]
142
196
  pumuki sdd session --close [--json]
143
- pumuki sdd sync-docs [--change=<change-id>] [--stage=PRE_WRITE|PRE_COMMIT|PRE_PUSH|CI] [--task=<task-id>] [--dry-run] [--json]
144
- pumuki sdd learn --change=<change-id> [--stage=PRE_WRITE|PRE_COMMIT|PRE_PUSH|CI] [--task=<task-id>] [--dry-run] [--json]
145
- pumuki sdd auto-sync --change=<change-id> [--stage=PRE_WRITE|PRE_COMMIT|PRE_PUSH|CI] [--task=<task-id>] [--dry-run] [--json]
197
+ pumuki sdd sync-docs [--change=<change-id>] [--stage=PRE_WRITE|PRE_COMMIT|PRE_PUSH|CI] [--task=<task-id>] [--from-evidence=<path>] [--dry-run] [--json]
198
+ pumuki sdd sync [--change=<change-id>] [--stage=PRE_WRITE|PRE_COMMIT|PRE_PUSH|CI] [--task=<task-id>] [--from-evidence=<path>] [--dry-run] [--json]
199
+ pumuki sdd learn --change=<change-id> [--stage=PRE_WRITE|PRE_COMMIT|PRE_PUSH|CI] [--task=<task-id>] [--from-evidence=<path>] [--dry-run] [--json]
200
+ pumuki sdd auto-sync --change=<change-id> [--stage=PRE_WRITE|PRE_COMMIT|PRE_PUSH|CI] [--task=<task-id>] [--from-evidence=<path>] [--dry-run] [--json]
201
+ pumuki sdd evidence --scenario-id=<id> --test-command=<command> --test-status=passed|failed [--test-output=<path>] [--from-evidence=<path>] [--dry-run] [--json]
202
+ pumuki sdd state-sync [--scenario-id=<id>] [--status=todo|in_progress|blocked|done] [--from-evidence=<path>] [--board-path=<path>] [--force] [--dry-run] [--json]
203
+ aliases de --stage: RED=PRE_WRITE, GREEN=PRE_COMMIT, REFACTOR=PRE_PUSH, CLOSE=CI
146
204
  `.trim();
147
205
 
148
206
  const LOOP_RUN_POLICY: GatePolicy = {
@@ -167,16 +225,19 @@ const withOptionalLocation = (message: string, location?: string): string => {
167
225
  };
168
226
 
169
227
  const isLifecycleCommand = (value: string): value is LifecycleCommand =>
228
+ value === 'bootstrap' ||
170
229
  value === 'install' ||
171
230
  value === 'uninstall' ||
172
231
  value === 'remove' ||
173
232
  value === 'update' ||
174
233
  value === 'doctor' ||
175
234
  value === 'status' ||
235
+ value === 'watch' ||
176
236
  value === 'loop' ||
177
237
  value === 'sdd' ||
178
238
  value === 'adapter' ||
179
- value === 'analytics';
239
+ value === 'analytics' ||
240
+ value === 'policy';
180
241
 
181
242
  const parseAdapterAgent = (value?: string): AdapterAgent => {
182
243
  const normalized = (value ?? '').trim();
@@ -188,17 +249,96 @@ const parseAdapterAgent = (value?: string): AdapterAgent => {
188
249
 
189
250
  const parseSddStage = (value?: string): SddStage => {
190
251
  const normalized = (value ?? 'PRE_COMMIT').trim().toUpperCase();
252
+ const aliases: Record<string, SddStage> = {
253
+ RED: 'PRE_WRITE',
254
+ GREEN: 'PRE_COMMIT',
255
+ REFACTOR: 'PRE_PUSH',
256
+ CLOSE: 'CI',
257
+ };
258
+ const mapped = aliases[normalized] ?? normalized;
191
259
  if (
192
- normalized === 'PRE_WRITE' ||
193
- normalized === 'PRE_COMMIT' ||
194
- normalized === 'PRE_PUSH' ||
195
- normalized === 'CI'
260
+ mapped === 'PRE_WRITE' ||
261
+ mapped === 'PRE_COMMIT' ||
262
+ mapped === 'PRE_PUSH' ||
263
+ mapped === 'CI'
196
264
  ) {
197
- return normalized;
265
+ return mapped;
198
266
  }
199
267
  throw new Error(`Unsupported SDD stage "${value}". Use PRE_WRITE, PRE_COMMIT, PRE_PUSH or CI.`);
200
268
  };
201
269
 
270
+ const parseSddEvidencePath = (value: string): string => {
271
+ const normalized = value.trim();
272
+ if (normalized.length === 0) {
273
+ throw new Error(`Invalid --from-evidence value "${value}".`);
274
+ }
275
+ return normalized;
276
+ };
277
+
278
+ const parseSddEvidenceTestStatus = (value: string): SddEvidenceScaffoldTestStatus => {
279
+ const normalized = value.trim().toLowerCase();
280
+ if (normalized === 'passed') {
281
+ return 'passed';
282
+ }
283
+ if (normalized === 'failed') {
284
+ return 'failed';
285
+ }
286
+ throw new Error(`Invalid --test-status value "${value}". Use passed|failed.`);
287
+ };
288
+
289
+ const parseSddStateSyncStatus = (value: string): SddStateSyncStatus => {
290
+ const normalized = value.trim().toLowerCase();
291
+ if (
292
+ normalized === 'todo' ||
293
+ normalized === 'in_progress' ||
294
+ normalized === 'blocked' ||
295
+ normalized === 'done'
296
+ ) {
297
+ return normalized;
298
+ }
299
+ throw new Error(
300
+ `Invalid --status value "${value}". Use todo|in_progress|blocked|done for "pumuki sdd state-sync".`
301
+ );
302
+ };
303
+
304
+ const parseWatchStage = (value?: string): LifecycleWatchStage => {
305
+ const normalized = (value ?? 'PRE_COMMIT').trim().toUpperCase();
306
+ if (normalized === 'PRE_COMMIT' || normalized === 'PRE_PUSH' || normalized === 'CI') {
307
+ return normalized;
308
+ }
309
+ throw new Error(`Unsupported watch stage "${value}". Use PRE_COMMIT, PRE_PUSH or CI.`);
310
+ };
311
+
312
+ const parseWatchScope = (value?: string): LifecycleWatchScope => {
313
+ const normalized = (value ?? 'workingTree').trim();
314
+ if (
315
+ normalized === 'workingTree' ||
316
+ normalized === 'staged' ||
317
+ normalized === 'repoAndStaged' ||
318
+ normalized === 'repo'
319
+ ) {
320
+ return normalized;
321
+ }
322
+ throw new Error(
323
+ `Unsupported watch scope "${value}". Use workingTree, staged, repoAndStaged or repo.`
324
+ );
325
+ };
326
+
327
+ const parseWatchSeverityThreshold = (value?: string): LifecycleWatchSeverityThreshold => {
328
+ const normalized = (value ?? 'high').trim().toLowerCase();
329
+ if (
330
+ normalized === 'critical' ||
331
+ normalized === 'high' ||
332
+ normalized === 'medium' ||
333
+ normalized === 'low'
334
+ ) {
335
+ return normalized;
336
+ }
337
+ throw new Error(
338
+ `Unsupported watch severity threshold "${value}". Use critical, high, medium or low.`
339
+ );
340
+ };
341
+
202
342
  const parseOutputPathFlag = (value: string, flagName: '--output-json' | '--output-markdown'): string => {
203
343
  const normalized = value.trim();
204
344
  if (normalized.length === 0) {
@@ -429,8 +569,19 @@ export const parseLifecycleCliArgs = (argv: ReadonlyArray<string>): ParsedArgs =
429
569
  let purgeArtifacts = false;
430
570
  let updateSpec: ParsedArgs['updateSpec'];
431
571
  let json = false;
572
+ let bootstrapEnterprise = false;
573
+ let bootstrapAgent: ParsedArgs['bootstrapAgent'];
574
+ let installWithMcp = false;
575
+ let installMcpAgent: ParsedArgs['installMcpAgent'];
432
576
  let remoteChecks = false;
433
577
  let doctorDeep = false;
578
+ let watchStage: ParsedArgs['watchStage'];
579
+ let watchScope: ParsedArgs['watchScope'];
580
+ let watchIntervalMs: ParsedArgs['watchIntervalMs'];
581
+ let watchNotifyCooldownMs: ParsedArgs['watchNotifyCooldownMs'];
582
+ let watchSeverityThreshold: ParsedArgs['watchSeverityThreshold'];
583
+ let watchNotifyEnabled: ParsedArgs['watchNotifyEnabled'];
584
+ let watchIterations: ParsedArgs['watchIterations'];
434
585
  let sddCommand: ParsedArgs['sddCommand'];
435
586
  let loopCommand: ParsedArgs['loopCommand'];
436
587
  let loopSessionId: ParsedArgs['loopSessionId'];
@@ -445,14 +596,29 @@ export const parseLifecycleCliArgs = (argv: ReadonlyArray<string>): ParsedArgs =
445
596
  let sddSyncDocsChange: ParsedArgs['sddSyncDocsChange'];
446
597
  let sddSyncDocsStage: ParsedArgs['sddSyncDocsStage'];
447
598
  let sddSyncDocsTask: ParsedArgs['sddSyncDocsTask'];
599
+ let sddSyncDocsFromEvidence: ParsedArgs['sddSyncDocsFromEvidence'];
448
600
  let sddLearnDryRun = false;
449
601
  let sddLearnChange: ParsedArgs['sddLearnChange'];
450
602
  let sddLearnStage: ParsedArgs['sddLearnStage'];
451
603
  let sddLearnTask: ParsedArgs['sddLearnTask'];
604
+ let sddLearnFromEvidence: ParsedArgs['sddLearnFromEvidence'];
452
605
  let sddAutoSyncDryRun = false;
453
606
  let sddAutoSyncChange: ParsedArgs['sddAutoSyncChange'];
454
607
  let sddAutoSyncStage: ParsedArgs['sddAutoSyncStage'];
455
608
  let sddAutoSyncTask: ParsedArgs['sddAutoSyncTask'];
609
+ let sddAutoSyncFromEvidence: ParsedArgs['sddAutoSyncFromEvidence'];
610
+ let sddEvidenceDryRun = false;
611
+ let sddEvidenceScenarioId: ParsedArgs['sddEvidenceScenarioId'];
612
+ let sddEvidenceTestCommand: ParsedArgs['sddEvidenceTestCommand'];
613
+ let sddEvidenceTestStatus: ParsedArgs['sddEvidenceTestStatus'];
614
+ let sddEvidenceTestOutput: ParsedArgs['sddEvidenceTestOutput'];
615
+ let sddEvidenceFromEvidence: ParsedArgs['sddEvidenceFromEvidence'];
616
+ let sddStateSyncDryRun = false;
617
+ let sddStateSyncScenarioId: ParsedArgs['sddStateSyncScenarioId'];
618
+ let sddStateSyncStatus: ParsedArgs['sddStateSyncStatus'];
619
+ let sddStateSyncFromEvidence: ParsedArgs['sddStateSyncFromEvidence'];
620
+ let sddStateSyncBoardPath: ParsedArgs['sddStateSyncBoardPath'];
621
+ let sddStateSyncForce = false;
456
622
  let adapterCommand: ParsedArgs['adapterCommand'];
457
623
  let adapterAgent: ParsedArgs['adapterAgent'];
458
624
  let adapterDryRun = false;
@@ -463,6 +629,78 @@ export const parseLifecycleCliArgs = (argv: ReadonlyArray<string>): ParsedArgs =
463
629
  let analyticsJsonOutputPath: ParsedArgs['analyticsJsonOutputPath'];
464
630
  let analyticsMarkdownOutputPath: ParsedArgs['analyticsMarkdownOutputPath'];
465
631
 
632
+ if (commandRaw === 'watch') {
633
+ for (const arg of argv.slice(1)) {
634
+ if (arg === '--json') {
635
+ json = true;
636
+ continue;
637
+ }
638
+ if (arg === '--no-notify') {
639
+ watchNotifyEnabled = false;
640
+ continue;
641
+ }
642
+ if (arg === '--once') {
643
+ watchIterations = 1;
644
+ continue;
645
+ }
646
+ if (arg.startsWith('--stage=')) {
647
+ watchStage = parseWatchStage(arg.slice('--stage='.length));
648
+ continue;
649
+ }
650
+ if (arg.startsWith('--scope=')) {
651
+ watchScope = parseWatchScope(arg.slice('--scope='.length));
652
+ continue;
653
+ }
654
+ if (arg.startsWith('--severity=')) {
655
+ watchSeverityThreshold = parseWatchSeverityThreshold(
656
+ arg.slice('--severity='.length)
657
+ );
658
+ continue;
659
+ }
660
+ if (arg.startsWith('--interval-ms=')) {
661
+ const parsedInterval = Number.parseInt(arg.slice('--interval-ms='.length), 10);
662
+ if (!Number.isInteger(parsedInterval) || parsedInterval <= 0) {
663
+ throw new Error(`Invalid --interval-ms value "${arg}".`);
664
+ }
665
+ watchIntervalMs = parsedInterval;
666
+ continue;
667
+ }
668
+ if (arg.startsWith('--notify-cooldown-ms=')) {
669
+ const parsedCooldown = Number.parseInt(
670
+ arg.slice('--notify-cooldown-ms='.length),
671
+ 10
672
+ );
673
+ if (!Number.isInteger(parsedCooldown) || parsedCooldown < 0) {
674
+ throw new Error(`Invalid --notify-cooldown-ms value "${arg}".`);
675
+ }
676
+ watchNotifyCooldownMs = parsedCooldown;
677
+ continue;
678
+ }
679
+ if (arg.startsWith('--iterations=')) {
680
+ const parsedIterations = Number.parseInt(arg.slice('--iterations='.length), 10);
681
+ if (!Number.isInteger(parsedIterations) || parsedIterations <= 0) {
682
+ throw new Error(`Invalid --iterations value "${arg}".`);
683
+ }
684
+ watchIterations = parsedIterations;
685
+ continue;
686
+ }
687
+ throw new Error(`Unsupported argument "${arg}".\n\n${HELP_TEXT}`);
688
+ }
689
+
690
+ return {
691
+ command: commandRaw,
692
+ purgeArtifacts: false,
693
+ json,
694
+ watchStage: watchStage ?? 'PRE_COMMIT',
695
+ watchScope: watchScope ?? 'workingTree',
696
+ watchIntervalMs: watchIntervalMs ?? 3000,
697
+ watchNotifyCooldownMs: watchNotifyCooldownMs ?? 30_000,
698
+ watchSeverityThreshold: watchSeverityThreshold ?? 'high',
699
+ watchNotifyEnabled: watchNotifyEnabled !== false,
700
+ ...(typeof watchIterations === 'number' ? { watchIterations } : {}),
701
+ };
702
+ }
703
+
466
704
  if (commandRaw === 'analytics') {
467
705
  const subcommandRaw = argv[1] ?? '';
468
706
  if (subcommandRaw !== 'hotspots') {
@@ -535,6 +773,38 @@ export const parseLifecycleCliArgs = (argv: ReadonlyArray<string>): ParsedArgs =
535
773
  return parsedAnalyticsArgs;
536
774
  }
537
775
 
776
+ if (commandRaw === 'policy') {
777
+ const firstArg = argv[1];
778
+ const subcommandRaw =
779
+ typeof firstArg === 'string' && !firstArg.startsWith('--')
780
+ ? firstArg
781
+ : 'reconcile';
782
+ if (subcommandRaw !== 'reconcile') {
783
+ throw new Error(`Unsupported policy subcommand "${subcommandRaw}".\n\n${HELP_TEXT}`);
784
+ }
785
+ let policyStrict = false;
786
+ const policyFlagsOffset =
787
+ typeof firstArg === 'string' && !firstArg.startsWith('--') ? 2 : 1;
788
+ for (const arg of argv.slice(policyFlagsOffset)) {
789
+ if (arg === '--json') {
790
+ json = true;
791
+ continue;
792
+ }
793
+ if (arg === '--strict') {
794
+ policyStrict = true;
795
+ continue;
796
+ }
797
+ throw new Error(`Unsupported argument "${arg}".\n\n${HELP_TEXT}`);
798
+ }
799
+ return {
800
+ command: commandRaw,
801
+ purgeArtifacts: false,
802
+ json,
803
+ policyCommand: 'reconcile',
804
+ policyStrict,
805
+ };
806
+ }
807
+
538
808
  if (commandRaw === 'loop') {
539
809
  const subcommandRaw = argv[1] ?? '';
540
810
  if (
@@ -632,12 +902,15 @@ export const parseLifecycleCliArgs = (argv: ReadonlyArray<string>): ParsedArgs =
632
902
  subcommandRaw !== 'validate' &&
633
903
  subcommandRaw !== 'session' &&
634
904
  subcommandRaw !== 'sync-docs' &&
905
+ subcommandRaw !== 'sync' &&
635
906
  subcommandRaw !== 'learn' &&
636
- subcommandRaw !== 'auto-sync'
907
+ subcommandRaw !== 'auto-sync' &&
908
+ subcommandRaw !== 'evidence' &&
909
+ subcommandRaw !== 'state-sync'
637
910
  ) {
638
911
  throw new Error(`Unsupported SDD subcommand "${subcommandRaw}".\n\n${HELP_TEXT}`);
639
912
  }
640
- sddCommand = subcommandRaw;
913
+ sddCommand = subcommandRaw === 'sync' ? 'sync-docs' : subcommandRaw;
641
914
 
642
915
  for (const arg of argv.slice(2)) {
643
916
  if (arg === '--json') {
@@ -657,7 +930,15 @@ export const parseLifecycleCliArgs = (argv: ReadonlyArray<string>): ParsedArgs =
657
930
  sddAutoSyncDryRun = true;
658
931
  continue;
659
932
  }
660
- throw new Error(`--dry-run is only supported with "pumuki sdd sync-docs", "pumuki sdd learn" or "pumuki sdd auto-sync".\n\n${HELP_TEXT}`);
933
+ if (sddCommand === 'evidence') {
934
+ sddEvidenceDryRun = true;
935
+ continue;
936
+ }
937
+ if (sddCommand === 'state-sync') {
938
+ sddStateSyncDryRun = true;
939
+ continue;
940
+ }
941
+ throw new Error(`--dry-run is only supported with "pumuki sdd sync-docs", "pumuki sdd learn", "pumuki sdd auto-sync", "pumuki sdd evidence" or "pumuki sdd state-sync".\n\n${HELP_TEXT}`);
661
942
  }
662
943
  if (arg.startsWith('--stage=')) {
663
944
  if (sddCommand === 'validate') {
@@ -678,6 +959,13 @@ export const parseLifecycleCliArgs = (argv: ReadonlyArray<string>): ParsedArgs =
678
959
  }
679
960
  throw new Error(`--stage is only supported with "pumuki sdd validate", "pumuki sdd sync-docs", "pumuki sdd learn" or "pumuki sdd auto-sync".\n\n${HELP_TEXT}`);
680
961
  }
962
+ if (arg.startsWith('--status=')) {
963
+ if (sddCommand !== 'state-sync') {
964
+ throw new Error(`--status is only supported with "pumuki sdd state-sync".\n\n${HELP_TEXT}`);
965
+ }
966
+ sddStateSyncStatus = parseSddStateSyncStatus(arg.slice('--status='.length));
967
+ continue;
968
+ }
681
969
  if (arg === '--open') {
682
970
  if (sddCommand !== 'session') {
683
971
  throw new Error(`--open is only supported with "pumuki sdd session".\n\n${HELP_TEXT}`);
@@ -757,6 +1045,104 @@ export const parseLifecycleCliArgs = (argv: ReadonlyArray<string>): ParsedArgs =
757
1045
  }
758
1046
  throw new Error(`--task is only supported with "pumuki sdd sync-docs", "pumuki sdd learn" or "pumuki sdd auto-sync".\n\n${HELP_TEXT}`);
759
1047
  }
1048
+ if (arg.startsWith('--scenario-id=')) {
1049
+ const scenarioId = arg.slice('--scenario-id='.length).trim();
1050
+ if (scenarioId.length === 0) {
1051
+ throw new Error(`Invalid --scenario-id value "${arg}".`);
1052
+ }
1053
+ if (sddCommand === 'evidence') {
1054
+ sddEvidenceScenarioId = scenarioId;
1055
+ continue;
1056
+ }
1057
+ if (sddCommand === 'state-sync') {
1058
+ sddStateSyncScenarioId = scenarioId;
1059
+ continue;
1060
+ }
1061
+ throw new Error(`--scenario-id is only supported with "pumuki sdd evidence" or "pumuki sdd state-sync".\n\n${HELP_TEXT}`);
1062
+ continue;
1063
+ }
1064
+ if (arg.startsWith('--test-command=')) {
1065
+ if (sddCommand !== 'evidence') {
1066
+ throw new Error(`--test-command is only supported with "pumuki sdd evidence".\n\n${HELP_TEXT}`);
1067
+ }
1068
+ const testCommand = arg.slice('--test-command='.length).trim();
1069
+ if (testCommand.length === 0) {
1070
+ throw new Error(`Invalid --test-command value "${arg}".`);
1071
+ }
1072
+ sddEvidenceTestCommand = testCommand;
1073
+ continue;
1074
+ }
1075
+ if (arg.startsWith('--test-status=')) {
1076
+ if (sddCommand !== 'evidence') {
1077
+ throw new Error(`--test-status is only supported with "pumuki sdd evidence".\n\n${HELP_TEXT}`);
1078
+ }
1079
+ sddEvidenceTestStatus = parseSddEvidenceTestStatus(arg.slice('--test-status='.length));
1080
+ continue;
1081
+ }
1082
+ if (arg.startsWith('--test-output=')) {
1083
+ if (sddCommand !== 'evidence') {
1084
+ throw new Error(`--test-output is only supported with "pumuki sdd evidence".\n\n${HELP_TEXT}`);
1085
+ }
1086
+ const testOutputPath = arg.slice('--test-output='.length).trim();
1087
+ if (testOutputPath.length === 0) {
1088
+ throw new Error(`Invalid --test-output value "${arg}".`);
1089
+ }
1090
+ sddEvidenceTestOutput = testOutputPath;
1091
+ continue;
1092
+ }
1093
+ if (arg.startsWith('--from-evidence=')) {
1094
+ if (sddCommand === 'sync-docs') {
1095
+ sddSyncDocsFromEvidence = parseSddEvidencePath(
1096
+ arg.slice('--from-evidence='.length)
1097
+ );
1098
+ continue;
1099
+ }
1100
+ if (sddCommand === 'learn') {
1101
+ sddLearnFromEvidence = parseSddEvidencePath(
1102
+ arg.slice('--from-evidence='.length)
1103
+ );
1104
+ continue;
1105
+ }
1106
+ if (sddCommand === 'auto-sync') {
1107
+ sddAutoSyncFromEvidence = parseSddEvidencePath(
1108
+ arg.slice('--from-evidence='.length)
1109
+ );
1110
+ continue;
1111
+ }
1112
+ if (sddCommand === 'evidence') {
1113
+ sddEvidenceFromEvidence = parseSddEvidencePath(
1114
+ arg.slice('--from-evidence='.length)
1115
+ );
1116
+ continue;
1117
+ }
1118
+ if (sddCommand === 'state-sync') {
1119
+ sddStateSyncFromEvidence = parseSddEvidencePath(
1120
+ arg.slice('--from-evidence='.length)
1121
+ );
1122
+ continue;
1123
+ }
1124
+ throw new Error(
1125
+ `--from-evidence is only supported with "pumuki sdd sync-docs", "pumuki sdd sync", "pumuki sdd learn", "pumuki sdd auto-sync", "pumuki sdd evidence" or "pumuki sdd state-sync".\n\n${HELP_TEXT}`
1126
+ );
1127
+ }
1128
+ if (arg.startsWith('--board-path=')) {
1129
+ if (sddCommand !== 'state-sync') {
1130
+ throw new Error(`--board-path is only supported with "pumuki sdd state-sync".\n\n${HELP_TEXT}`);
1131
+ }
1132
+ const boardPath = arg.slice('--board-path='.length).trim();
1133
+ if (boardPath.length === 0) {
1134
+ throw new Error(`Invalid --board-path value "${arg}".`);
1135
+ }
1136
+ sddStateSyncBoardPath = boardPath;
1137
+ continue;
1138
+ }
1139
+ if (arg === '--force') {
1140
+ if (sddCommand !== 'state-sync') {
1141
+ throw new Error(`--force is only supported with "pumuki sdd state-sync".\n\n${HELP_TEXT}`);
1142
+ }
1143
+ sddStateSyncForce = true;
1144
+ continue;
1145
+ }
760
1146
  if (arg.startsWith('--ttl-minutes=')) {
761
1147
  if (sddCommand !== 'session') {
762
1148
  throw new Error(`--ttl-minutes is only supported with "pumuki sdd session".\n\n${HELP_TEXT}`);
@@ -789,9 +1175,13 @@ export const parseLifecycleCliArgs = (argv: ReadonlyArray<string>): ParsedArgs =
789
1175
  };
790
1176
  }
791
1177
  if (sddCommand === 'sync-docs') {
792
- if (sddSessionAction || sddChangeId || typeof sddTtlMinutes === 'number') {
1178
+ if (
1179
+ sddSessionAction ||
1180
+ sddChangeId ||
1181
+ typeof sddTtlMinutes === 'number'
1182
+ ) {
793
1183
  throw new Error(
794
- `"pumuki sdd sync-docs" only supports [--change=<change-id>] [--stage=PRE_WRITE|PRE_COMMIT|PRE_PUSH|CI] [--task=<task-id>] [--dry-run] [--json].\n\n${HELP_TEXT}`
1184
+ `"pumuki sdd sync-docs" only supports [--change=<change-id>] [--stage=PRE_WRITE|PRE_COMMIT|PRE_PUSH|CI] [--task=<task-id>] [--from-evidence=<path>] [--dry-run] [--json].\n\n${HELP_TEXT}`
795
1185
  );
796
1186
  }
797
1187
  return {
@@ -803,12 +1193,15 @@ export const parseLifecycleCliArgs = (argv: ReadonlyArray<string>): ParsedArgs =
803
1193
  ...(sddSyncDocsChange ? { sddSyncDocsChange } : {}),
804
1194
  ...(sddSyncDocsStage ? { sddSyncDocsStage } : {}),
805
1195
  ...(sddSyncDocsTask ? { sddSyncDocsTask } : {}),
1196
+ ...(sddSyncDocsFromEvidence
1197
+ ? { sddSyncDocsFromEvidence }
1198
+ : {}),
806
1199
  };
807
1200
  }
808
1201
  if (sddCommand === 'learn') {
809
1202
  if (sddSessionAction || sddChangeId || typeof sddTtlMinutes === 'number') {
810
1203
  throw new Error(
811
- `"pumuki sdd learn" only supports --change=<change-id> [--stage=PRE_WRITE|PRE_COMMIT|PRE_PUSH|CI] [--task=<task-id>] [--dry-run] [--json].\n\n${HELP_TEXT}`
1204
+ `"pumuki sdd learn" only supports --change=<change-id> [--stage=PRE_WRITE|PRE_COMMIT|PRE_PUSH|CI] [--task=<task-id>] [--from-evidence=<path>] [--dry-run] [--json].\n\n${HELP_TEXT}`
812
1205
  );
813
1206
  }
814
1207
  if (!sddLearnChange || sddLearnChange.length === 0) {
@@ -823,12 +1216,13 @@ export const parseLifecycleCliArgs = (argv: ReadonlyArray<string>): ParsedArgs =
823
1216
  sddLearnChange,
824
1217
  ...(sddLearnStage ? { sddLearnStage } : {}),
825
1218
  ...(sddLearnTask ? { sddLearnTask } : {}),
1219
+ ...(sddLearnFromEvidence ? { sddLearnFromEvidence } : {}),
826
1220
  };
827
1221
  }
828
1222
  if (sddCommand === 'auto-sync') {
829
1223
  if (sddSessionAction || sddChangeId || typeof sddTtlMinutes === 'number') {
830
1224
  throw new Error(
831
- `"pumuki sdd auto-sync" only supports --change=<change-id> [--stage=PRE_WRITE|PRE_COMMIT|PRE_PUSH|CI] [--task=<task-id>] [--dry-run] [--json].\n\n${HELP_TEXT}`
1225
+ `"pumuki sdd auto-sync" only supports --change=<change-id> [--stage=PRE_WRITE|PRE_COMMIT|PRE_PUSH|CI] [--task=<task-id>] [--from-evidence=<path>] [--dry-run] [--json].\n\n${HELP_TEXT}`
832
1226
  );
833
1227
  }
834
1228
  if (!sddAutoSyncChange || sddAutoSyncChange.length === 0) {
@@ -843,6 +1237,68 @@ export const parseLifecycleCliArgs = (argv: ReadonlyArray<string>): ParsedArgs =
843
1237
  sddAutoSyncChange,
844
1238
  ...(sddAutoSyncStage ? { sddAutoSyncStage } : {}),
845
1239
  ...(sddAutoSyncTask ? { sddAutoSyncTask } : {}),
1240
+ ...(sddAutoSyncFromEvidence ? { sddAutoSyncFromEvidence } : {}),
1241
+ };
1242
+ }
1243
+ if (sddCommand === 'evidence') {
1244
+ if (
1245
+ sddSessionAction ||
1246
+ sddChangeId ||
1247
+ typeof sddTtlMinutes === 'number' ||
1248
+ sddSyncDocsChange ||
1249
+ sddLearnChange ||
1250
+ sddAutoSyncChange
1251
+ ) {
1252
+ throw new Error(
1253
+ `"pumuki sdd evidence" only supports --scenario-id=<id> --test-command=<command> --test-status=passed|failed [--test-output=<path>] [--from-evidence=<path>] [--dry-run] [--json].\n\n${HELP_TEXT}`
1254
+ );
1255
+ }
1256
+ if (!sddEvidenceScenarioId) {
1257
+ throw new Error(`Missing --scenario-id=<id> for "pumuki sdd evidence".\n\n${HELP_TEXT}`);
1258
+ }
1259
+ if (!sddEvidenceTestCommand) {
1260
+ throw new Error(`Missing --test-command=<command> for "pumuki sdd evidence".\n\n${HELP_TEXT}`);
1261
+ }
1262
+ if (!sddEvidenceTestStatus) {
1263
+ throw new Error(`Missing --test-status=passed|failed for "pumuki sdd evidence".\n\n${HELP_TEXT}`);
1264
+ }
1265
+ return {
1266
+ command: commandRaw,
1267
+ purgeArtifacts: false,
1268
+ json,
1269
+ sddCommand,
1270
+ sddEvidenceDryRun,
1271
+ sddEvidenceScenarioId,
1272
+ sddEvidenceTestCommand,
1273
+ sddEvidenceTestStatus,
1274
+ ...(sddEvidenceTestOutput ? { sddEvidenceTestOutput } : {}),
1275
+ ...(sddEvidenceFromEvidence ? { sddEvidenceFromEvidence } : {}),
1276
+ };
1277
+ }
1278
+ if (sddCommand === 'state-sync') {
1279
+ if (
1280
+ sddSessionAction ||
1281
+ sddChangeId ||
1282
+ typeof sddTtlMinutes === 'number' ||
1283
+ sddSyncDocsChange ||
1284
+ sddLearnChange ||
1285
+ sddAutoSyncChange
1286
+ ) {
1287
+ throw new Error(
1288
+ `"pumuki sdd state-sync" only supports [--scenario-id=<id>] [--status=todo|in_progress|blocked|done] [--from-evidence=<path>] [--board-path=<path>] [--force] [--dry-run] [--json].\n\n${HELP_TEXT}`
1289
+ );
1290
+ }
1291
+ return {
1292
+ command: commandRaw,
1293
+ purgeArtifacts: false,
1294
+ json,
1295
+ sddCommand,
1296
+ sddStateSyncDryRun,
1297
+ ...(sddStateSyncScenarioId ? { sddStateSyncScenarioId } : {}),
1298
+ ...(sddStateSyncStatus ? { sddStateSyncStatus } : {}),
1299
+ ...(sddStateSyncFromEvidence ? { sddStateSyncFromEvidence } : {}),
1300
+ ...(sddStateSyncBoardPath ? { sddStateSyncBoardPath } : {}),
1301
+ ...(sddStateSyncForce ? { sddStateSyncForce: true } : {}),
846
1302
  };
847
1303
  }
848
1304
 
@@ -908,6 +1364,31 @@ export const parseLifecycleCliArgs = (argv: ReadonlyArray<string>): ParsedArgs =
908
1364
  json = true;
909
1365
  continue;
910
1366
  }
1367
+ if (arg === '--with-mcp') {
1368
+ if (commandRaw !== 'install') {
1369
+ throw new Error(`--with-mcp is only supported with "pumuki install".\n\n${HELP_TEXT}`);
1370
+ }
1371
+ installWithMcp = true;
1372
+ continue;
1373
+ }
1374
+ if (arg === '--enterprise') {
1375
+ if (commandRaw !== 'bootstrap') {
1376
+ throw new Error(`--enterprise is only supported with "pumuki bootstrap".\n\n${HELP_TEXT}`);
1377
+ }
1378
+ bootstrapEnterprise = true;
1379
+ continue;
1380
+ }
1381
+ if (arg.startsWith('--agent=')) {
1382
+ if (commandRaw === 'install') {
1383
+ installMcpAgent = parseAdapterAgent(arg.slice('--agent='.length).trim());
1384
+ continue;
1385
+ }
1386
+ if (commandRaw === 'bootstrap') {
1387
+ bootstrapAgent = parseAdapterAgent(arg.slice('--agent='.length).trim());
1388
+ continue;
1389
+ }
1390
+ throw new Error(`Unsupported argument "${arg}".\n\n${HELP_TEXT}`);
1391
+ }
911
1392
  if (arg === '--remote-checks') {
912
1393
  remoteChecks = true;
913
1394
  continue;
@@ -938,12 +1419,31 @@ export const parseLifecycleCliArgs = (argv: ReadonlyArray<string>): ParsedArgs =
938
1419
  if (doctorDeep && commandRaw !== 'doctor') {
939
1420
  throw new Error(`--deep is only supported with "pumuki doctor".\n\n${HELP_TEXT}`);
940
1421
  }
1422
+ if (commandRaw !== 'bootstrap' && bootstrapEnterprise) {
1423
+ throw new Error(`--enterprise is only supported with "pumuki bootstrap".\n\n${HELP_TEXT}`);
1424
+ }
1425
+ if (commandRaw !== 'bootstrap' && bootstrapAgent) {
1426
+ throw new Error(`--agent is only supported with "pumuki bootstrap" or "pumuki install --with-mcp".\n\n${HELP_TEXT}`);
1427
+ }
1428
+ if (commandRaw !== 'install' && installWithMcp) {
1429
+ throw new Error(`--with-mcp is only supported with "pumuki install".\n\n${HELP_TEXT}`);
1430
+ }
1431
+ if (commandRaw !== 'install' && installMcpAgent) {
1432
+ throw new Error(`--agent is only supported with "pumuki install --with-mcp".\n\n${HELP_TEXT}`);
1433
+ }
1434
+ if (commandRaw === 'install' && installMcpAgent && !installWithMcp) {
1435
+ throw new Error(`--agent is only supported with "pumuki install --with-mcp".\n\n${HELP_TEXT}`);
1436
+ }
941
1437
 
942
1438
  return {
943
1439
  command: commandRaw,
944
1440
  purgeArtifacts,
945
1441
  updateSpec,
946
1442
  json,
1443
+ ...(commandRaw === 'bootstrap' ? { bootstrapEnterprise: true } : {}),
1444
+ ...(bootstrapAgent ? { bootstrapAgent } : {}),
1445
+ ...(installWithMcp ? { installWithMcp: true } : {}),
1446
+ ...(installMcpAgent ? { installMcpAgent } : {}),
947
1447
  ...(remoteChecks ? { remoteChecks: true } : {}),
948
1448
  ...(doctorDeep ? { doctorDeep: true } : {}),
949
1449
  };
@@ -1063,6 +1563,7 @@ type LifecycleCliDependencies = {
1063
1563
  emitAuditSummaryNotificationFromAiGate: typeof emitAuditSummaryNotificationFromAiGate;
1064
1564
  emitGateBlockedNotification: typeof emitGateBlockedNotification;
1065
1565
  runPlatformGate: typeof runPlatformGate;
1566
+ runLifecycleWatch: typeof runLifecycleWatch;
1066
1567
  collectRemoteCiDiagnostics: typeof collectRemoteCiDiagnostics;
1067
1568
  };
1068
1569
 
@@ -1070,18 +1571,26 @@ const defaultLifecycleCliDependencies: LifecycleCliDependencies = {
1070
1571
  emitAuditSummaryNotificationFromAiGate,
1071
1572
  emitGateBlockedNotification,
1072
1573
  runPlatformGate,
1574
+ runLifecycleWatch,
1073
1575
  collectRemoteCiDiagnostics,
1074
1576
  };
1075
1577
 
1076
1578
  const PRE_WRITE_HINTS_BY_CODE: Readonly<Record<string, string>> = {
1077
1579
  EVIDENCE_MISSING: 'Regenera evidencia ejecutando una auditoría completa antes de continuar.',
1078
1580
  EVIDENCE_INVALID: 'Corrige el contrato de .ai_evidence.json y vuelve a ejecutar el gate.',
1581
+ EVIDENCE_INTEGRITY_MISSING: 'Refresca evidencia para regenerar metadatos de integridad.',
1079
1582
  EVIDENCE_CHAIN_INVALID: 'Regenera evidencia para restablecer la cadena criptográfica íntegra.',
1080
1583
  EVIDENCE_STALE: 'Refresca evidencia para este repo y rama.',
1081
1584
  EVIDENCE_REPO_ROOT_MISMATCH: 'Regenera evidencia desde este repositorio.',
1082
1585
  EVIDENCE_BRANCH_MISMATCH: 'Regenera evidencia en la rama actual.',
1083
1586
  EVIDENCE_RULES_COVERAGE_MISSING: 'Ejecuta auditoría completa para recalcular rules_coverage.',
1084
1587
  EVIDENCE_RULES_COVERAGE_INCOMPLETE: 'Asegura unevaluated=0 y coverage_ratio=1.',
1588
+ EVIDENCE_SKILLS_CONTRACT_INCOMPLETE:
1589
+ 'Completa contrato skills/policy para el stage actual y vuelve a validar.',
1590
+ EVIDENCE_PREWRITE_WORKTREE_OVER_LIMIT:
1591
+ 'Reduce el worktree pendiente en slices atómicos antes de continuar.',
1592
+ EVIDENCE_PREWRITE_WORKTREE_WARN:
1593
+ 'Particiona cambios ahora para evitar bloqueos tardíos en commit/push.',
1085
1594
  GITFLOW_PROTECTED_BRANCH: 'Trabaja en feature/* y evita ramas protegidas.',
1086
1595
  MCP_ENTERPRISE_RECEIPT_MISSING: 'Invoca ai_gate_check desde pumuki-enterprise MCP antes de PRE_WRITE.',
1087
1596
  MCP_ENTERPRISE_RECEIPT_INVALID: 'Corrige recibo MCP y vuelve a invocar ai_gate_check.',
@@ -1187,6 +1696,7 @@ const buildPreWriteValidationPanel = (params: {
1187
1696
  `Evidence: kind=${params.aiGate.evidence.kind} age=${params.aiGate.evidence.age_seconds ?? 'n/a'}s max=${params.aiGate.evidence.max_age_seconds}s`,
1188
1697
  `Evidence source: source=${params.aiGate.evidence.source.source} path=${params.aiGate.evidence.source.path} digest=${params.aiGate.evidence.source.digest ?? 'null'} generated_at=${params.aiGate.evidence.source.generated_at ?? 'null'}`,
1189
1698
  `MCP receipt: required=${receipt.required ? 'yes' : 'no'} kind=${receipt.kind} age=${receipt.age_seconds ?? 'n/a'}s max=${receipt.max_age_seconds ?? 'n/a'}s`,
1699
+ `Skills contract: enforced=${params.aiGate.skills_contract?.enforced ? 'yes' : 'no'} status=${params.aiGate.skills_contract?.status ?? 'n/a'} platforms=${params.aiGate.skills_contract?.detected_platforms.join(',') ?? 'none'}`,
1190
1700
  `Auto-heal: attempted=${params.automation.attempted ? 'yes' : 'no'} actions=${params.automation.actions.length}`,
1191
1701
  `Violations: ${params.aiGate.violations.length}`,
1192
1702
  ];
@@ -1322,6 +1832,98 @@ export const runLifecycleCli = async (
1322
1832
  const parsed = parseLifecycleCliArgs(argv);
1323
1833
 
1324
1834
  switch (parsed.command) {
1835
+ case 'bootstrap': {
1836
+ const installResult = runLifecycleInstall();
1837
+ const agent = parsed.bootstrapAgent ?? 'codex';
1838
+ const adapterResult = runLifecycleAdapterInstall({
1839
+ agent,
1840
+ });
1841
+ const doctorReport = runLifecycleDoctor({
1842
+ deep: true,
1843
+ });
1844
+ const adapterCheck = doctorReport.deep?.checks.find(
1845
+ (check) => check.id === 'adapter-wiring'
1846
+ );
1847
+ const blocking = doctorHasBlockingIssues(doctorReport);
1848
+
1849
+ if (parsed.json) {
1850
+ writeInfo(
1851
+ JSON.stringify(
1852
+ {
1853
+ command: 'bootstrap',
1854
+ enterprise: parsed.bootstrapEnterprise === true,
1855
+ install: {
1856
+ repo_root: installResult.repoRoot,
1857
+ version: installResult.version,
1858
+ hooks_changed: installResult.changedHooks,
1859
+ openspec: installResult.openSpecBootstrap
1860
+ ? {
1861
+ installed: installResult.openSpecBootstrap.packageInstalled,
1862
+ project_initialized: installResult.openSpecBootstrap.projectInitialized,
1863
+ actions: installResult.openSpecBootstrap.actions,
1864
+ skipped_reason: installResult.openSpecBootstrap.skippedReason ?? null,
1865
+ }
1866
+ : null,
1867
+ },
1868
+ mcp: {
1869
+ agent: adapterResult.agent,
1870
+ changed_files: adapterResult.changedFiles,
1871
+ adapter_health: adapterCheck
1872
+ ? {
1873
+ status: adapterCheck.status,
1874
+ severity: adapterCheck.severity,
1875
+ message: adapterCheck.message,
1876
+ remediation: adapterCheck.remediation ?? null,
1877
+ }
1878
+ : null,
1879
+ },
1880
+ doctor: {
1881
+ blocking,
1882
+ issues: doctorReport.issues,
1883
+ deep: doctorReport.deep ?? null,
1884
+ },
1885
+ },
1886
+ null,
1887
+ 2
1888
+ )
1889
+ );
1890
+ } else {
1891
+ writeInfo(
1892
+ `[pumuki] bootstrap enterprise: repo=${installResult.repoRoot} version=${installResult.version}`
1893
+ );
1894
+ writeInfo(
1895
+ `[pumuki] bootstrap install: hooks changed=${installResult.changedHooks.join(', ') || 'none'}`
1896
+ );
1897
+ if (installResult.openSpecBootstrap) {
1898
+ writeInfo(
1899
+ `[pumuki] bootstrap openspec: installed=${installResult.openSpecBootstrap.packageInstalled ? 'yes' : 'no'} project=${installResult.openSpecBootstrap.projectInitialized ? 'yes' : 'no'} actions=${installResult.openSpecBootstrap.actions.join(', ') || 'none'}`
1900
+ );
1901
+ if (installResult.openSpecBootstrap.skippedReason === 'NO_PACKAGE_JSON') {
1902
+ writeInfo('[pumuki] bootstrap openspec skipped npm install (package.json not found)');
1903
+ }
1904
+ }
1905
+ writeInfo(
1906
+ `[pumuki] bootstrap mcp: agent=${adapterResult.agent} changed=${adapterResult.changedFiles.length}`
1907
+ );
1908
+ if (adapterResult.changedFiles.length > 0) {
1909
+ writeInfo(`[pumuki] bootstrap mcp files: ${adapterResult.changedFiles.join(', ')}`);
1910
+ }
1911
+ if (adapterCheck) {
1912
+ writeInfo(
1913
+ `[pumuki] bootstrap doctor deep adapter-wiring: status=${adapterCheck.status.toUpperCase()} severity=${adapterCheck.severity.toUpperCase()} message=${adapterCheck.message}`
1914
+ );
1915
+ if (adapterCheck.remediation) {
1916
+ writeInfo(`[pumuki] bootstrap doctor remediation: ${adapterCheck.remediation}`);
1917
+ }
1918
+ }
1919
+ }
1920
+
1921
+ if (blocking) {
1922
+ writeError('[pumuki] bootstrap enterprise detected blocking issues. Review doctor output.');
1923
+ return 1;
1924
+ }
1925
+ return 0;
1926
+ }
1325
1927
  case 'install': {
1326
1928
  const result = runLifecycleInstall();
1327
1929
  writeInfo(
@@ -1335,6 +1937,31 @@ export const runLifecycleCli = async (
1335
1937
  writeInfo('[pumuki] openspec bootstrap skipped npm install (package.json not found)');
1336
1938
  }
1337
1939
  }
1940
+ if (parsed.installWithMcp) {
1941
+ const adapterResult = runLifecycleAdapterInstall({
1942
+ agent: parsed.installMcpAgent ?? 'codex',
1943
+ });
1944
+ writeInfo(
1945
+ `[pumuki] mcp wiring: agent=${adapterResult.agent} changed=${adapterResult.changedFiles.length}`
1946
+ );
1947
+ if (adapterResult.changedFiles.length > 0) {
1948
+ writeInfo(`[pumuki] mcp files: ${adapterResult.changedFiles.join(', ')}`);
1949
+ }
1950
+ const deepReport = runLifecycleDoctor({
1951
+ deep: true,
1952
+ });
1953
+ const adapterCheck = deepReport.deep?.checks.find(
1954
+ (check) => check.id === 'adapter-wiring'
1955
+ );
1956
+ if (adapterCheck) {
1957
+ writeInfo(
1958
+ `[pumuki] mcp health: status=${adapterCheck.status.toUpperCase()} severity=${adapterCheck.severity.toUpperCase()} message=${adapterCheck.message}`
1959
+ );
1960
+ if (adapterCheck.remediation) {
1961
+ writeInfo(`[pumuki] mcp health remediation: ${adapterCheck.remediation}`);
1962
+ }
1963
+ }
1964
+ }
1338
1965
  return 0;
1339
1966
  }
1340
1967
  case 'uninstall': {
@@ -1445,6 +2072,35 @@ export const runLifecycleCli = async (
1445
2072
  }
1446
2073
  return 0;
1447
2074
  }
2075
+ case 'watch': {
2076
+ const watchResult = await activeDependencies.runLifecycleWatch(
2077
+ {
2078
+ repoRoot: process.cwd(),
2079
+ stage: parsed.watchStage,
2080
+ scope: parsed.watchScope,
2081
+ intervalMs: parsed.watchIntervalMs,
2082
+ notifyCooldownMs: parsed.watchNotifyCooldownMs,
2083
+ severityThreshold: parsed.watchSeverityThreshold,
2084
+ notifyEnabled: parsed.watchNotifyEnabled,
2085
+ maxIterations: parsed.watchIterations,
2086
+ onTick: parsed.json
2087
+ ? undefined
2088
+ : (tick) => {
2089
+ writeInfo(
2090
+ `[pumuki][watch] tick=${tick.tick} stage=${tick.stage} scope=${tick.scope} changed=${tick.changed ? 'yes' : 'no'} evaluated=${tick.evaluated ? 'yes' : 'no'} exit=${tick.gateExitCode ?? 'n/a'} threshold=${tick.threshold} matched=${tick.findingsAtOrAboveThreshold} notification=${tick.notification}`
2091
+ );
2092
+ },
2093
+ }
2094
+ );
2095
+ if (parsed.json) {
2096
+ writeInfo(JSON.stringify(watchResult, null, 2));
2097
+ } else {
2098
+ writeInfo(
2099
+ `[pumuki][watch] done ticks=${watchResult.ticks} evaluations=${watchResult.evaluations} sent=${watchResult.notificationsSent} suppressed=${watchResult.notificationsSuppressed}`
2100
+ );
2101
+ }
2102
+ return 0;
2103
+ }
1448
2104
  case 'loop': {
1449
2105
  const repoRoot = process.cwd();
1450
2106
  if (parsed.loopCommand === 'run') {
@@ -1666,6 +2322,31 @@ export const runLifecycleCli = async (
1666
2322
  }
1667
2323
  return 1;
1668
2324
  }
2325
+ case 'policy': {
2326
+ if (parsed.policyCommand === 'reconcile') {
2327
+ const report = runPolicyReconcile({
2328
+ repoRoot: process.cwd(),
2329
+ strict: parsed.policyStrict === true,
2330
+ });
2331
+ if (parsed.json) {
2332
+ writeInfo(JSON.stringify(report, null, 2));
2333
+ } else {
2334
+ writeInfo(
2335
+ `[pumuki][policy] reconcile status=${report.summary.status} total=${report.summary.total} blocking=${report.summary.blocking} warnings=${report.summary.warnings} strict_requested=${report.strictRequested ? 'yes' : 'no'}`
2336
+ );
2337
+ for (const drift of report.drifts) {
2338
+ writeInfo(
2339
+ `[pumuki][policy] ${drift.code} severity=${drift.severity} blocking=${drift.blocking ? 'yes' : 'no'} message=${drift.message}`
2340
+ );
2341
+ if (drift.remediation) {
2342
+ writeInfo(`[pumuki][policy] remediation: ${drift.remediation}`);
2343
+ }
2344
+ }
2345
+ }
2346
+ return report.summary.blocking > 0 ? 1 : 0;
2347
+ }
2348
+ return 1;
2349
+ }
1669
2350
  case 'sdd': {
1670
2351
  if (parsed.sddCommand === 'status') {
1671
2352
  const sddStatus = readSddStatus();
@@ -1917,12 +2598,13 @@ export const runLifecycleCli = async (
1917
2598
  change: parsed.sddSyncDocsChange,
1918
2599
  stage: parsed.sddSyncDocsStage,
1919
2600
  task: parsed.sddSyncDocsTask,
2601
+ fromEvidencePath: parsed.sddSyncDocsFromEvidence,
1920
2602
  });
1921
2603
  if (parsed.json) {
1922
2604
  writeInfo(JSON.stringify(syncResult, null, 2));
1923
2605
  } else {
1924
2606
  writeInfo(
1925
- `[pumuki][sdd] sync-docs dry_run=${syncResult.dryRun ? 'yes' : 'no'} updated=${syncResult.updated ? 'yes' : 'no'} files=${syncResult.files.length} change=${syncResult.context.change ?? 'none'} stage=${syncResult.context.stage ?? 'none'} task=${syncResult.context.task ?? 'none'}`
2607
+ `[pumuki][sdd] sync-docs dry_run=${syncResult.dryRun ? 'yes' : 'no'} updated=${syncResult.updated ? 'yes' : 'no'} files=${syncResult.files.length} change=${syncResult.context.change ?? 'none'} stage=${syncResult.context.stage ?? 'none'} task=${syncResult.context.task ?? 'none'} from_evidence=${syncResult.context.fromEvidencePath ?? 'default'}`
1926
2608
  );
1927
2609
  for (const file of syncResult.files) {
1928
2610
  writeInfo(
@@ -1942,12 +2624,13 @@ export const runLifecycleCli = async (
1942
2624
  change: parsed.sddLearnChange,
1943
2625
  stage: parsed.sddLearnStage,
1944
2626
  task: parsed.sddLearnTask,
2627
+ fromEvidencePath: parsed.sddLearnFromEvidence,
1945
2628
  });
1946
2629
  if (parsed.json) {
1947
2630
  writeInfo(JSON.stringify(learnResult, null, 2));
1948
2631
  } else {
1949
2632
  writeInfo(
1950
- `[pumuki][sdd] learn dry_run=${learnResult.dryRun ? 'yes' : 'no'} change=${learnResult.context.change} stage=${learnResult.context.stage ?? 'none'} task=${learnResult.context.task ?? 'none'} written=${learnResult.learning.written ? 'yes' : 'no'} digest=${learnResult.learning.digest}`
2633
+ `[pumuki][sdd] learn dry_run=${learnResult.dryRun ? 'yes' : 'no'} change=${learnResult.context.change} stage=${learnResult.context.stage ?? 'none'} task=${learnResult.context.task ?? 'none'} from_evidence=${learnResult.context.fromEvidencePath ?? 'default'} written=${learnResult.learning.written ? 'yes' : 'no'} digest=${learnResult.learning.digest}`
1951
2634
  );
1952
2635
  writeInfo(
1953
2636
  `[pumuki][sdd] learning_path=${learnResult.learning.path} failed=${learnResult.learning.artifact.failed_patterns.length} successful=${learnResult.learning.artifact.successful_patterns.length} anomalies=${learnResult.learning.artifact.gate_anomalies.length} updates=${learnResult.learning.artifact.rule_updates.length}`
@@ -1962,12 +2645,13 @@ export const runLifecycleCli = async (
1962
2645
  change: parsed.sddAutoSyncChange,
1963
2646
  stage: parsed.sddAutoSyncStage,
1964
2647
  task: parsed.sddAutoSyncTask,
2648
+ fromEvidencePath: parsed.sddAutoSyncFromEvidence,
1965
2649
  });
1966
2650
  if (parsed.json) {
1967
2651
  writeInfo(JSON.stringify(autoSyncResult, null, 2));
1968
2652
  } else {
1969
2653
  writeInfo(
1970
- `[pumuki][sdd] auto-sync dry_run=${autoSyncResult.dryRun ? 'yes' : 'no'} change=${autoSyncResult.context.change} stage=${autoSyncResult.context.stage ?? 'none'} task=${autoSyncResult.context.task ?? 'none'} updated=${autoSyncResult.syncDocs.updated ? 'yes' : 'no'} files=${autoSyncResult.syncDocs.files.length} learning_written=${autoSyncResult.learning.written ? 'yes' : 'no'}`
2654
+ `[pumuki][sdd] auto-sync dry_run=${autoSyncResult.dryRun ? 'yes' : 'no'} change=${autoSyncResult.context.change} stage=${autoSyncResult.context.stage ?? 'none'} task=${autoSyncResult.context.task ?? 'none'} from_evidence=${autoSyncResult.context.fromEvidencePath ?? 'default'} updated=${autoSyncResult.syncDocs.updated ? 'yes' : 'no'} files=${autoSyncResult.syncDocs.files.length} learning_written=${autoSyncResult.learning.written ? 'yes' : 'no'}`
1971
2655
  );
1972
2656
  writeInfo(
1973
2657
  `[pumuki][sdd] learning_path=${autoSyncResult.learning.path} digest=${autoSyncResult.learning.digest}`
@@ -1980,6 +2664,59 @@ export const runLifecycleCli = async (
1980
2664
  }
1981
2665
  return 0;
1982
2666
  }
2667
+ if (parsed.sddCommand === 'evidence') {
2668
+ const evidenceResult = runSddEvidenceScaffold({
2669
+ repoRoot: process.cwd(),
2670
+ dryRun: parsed.sddEvidenceDryRun === true,
2671
+ scenarioId: parsed.sddEvidenceScenarioId,
2672
+ testCommand: parsed.sddEvidenceTestCommand,
2673
+ testStatus: parsed.sddEvidenceTestStatus,
2674
+ testOutputPath: parsed.sddEvidenceTestOutput,
2675
+ fromEvidencePath: parsed.sddEvidenceFromEvidence,
2676
+ });
2677
+ if (parsed.json) {
2678
+ writeInfo(JSON.stringify(evidenceResult, null, 2));
2679
+ } else {
2680
+ writeInfo(
2681
+ `[pumuki][sdd] evidence dry_run=${evidenceResult.dryRun ? 'yes' : 'no'} scenario=${evidenceResult.context.scenarioId} status=${evidenceResult.context.testStatus} output=${evidenceResult.output.path} written=${evidenceResult.output.written ? 'yes' : 'no'}`
2682
+ );
2683
+ writeInfo(
2684
+ `[pumuki][sdd] test_command=${evidenceResult.context.testCommand} test_output=${evidenceResult.context.testOutputPath ?? 'none'} from_evidence=${evidenceResult.context.fromEvidencePath ?? 'default'}`
2685
+ );
2686
+ writeInfo(
2687
+ `[pumuki][sdd] source_digest=${evidenceResult.artifact.ai_evidence.digest} generated_at=${evidenceResult.artifact.generated_at}`
2688
+ );
2689
+ }
2690
+ return 0;
2691
+ }
2692
+ if (parsed.sddCommand === 'state-sync') {
2693
+ const stateSyncResult = runSddStateSync({
2694
+ repoRoot: process.cwd(),
2695
+ dryRun: parsed.sddStateSyncDryRun === true,
2696
+ scenarioId: parsed.sddStateSyncScenarioId,
2697
+ status: parsed.sddStateSyncStatus,
2698
+ fromEvidencePath: parsed.sddStateSyncFromEvidence,
2699
+ boardPath: parsed.sddStateSyncBoardPath,
2700
+ force: parsed.sddStateSyncForce === true,
2701
+ });
2702
+ if (parsed.json) {
2703
+ writeInfo(JSON.stringify(stateSyncResult, null, 2));
2704
+ } else {
2705
+ writeInfo(
2706
+ `[pumuki][sdd] state-sync dry_run=${stateSyncResult.dryRun ? 'yes' : 'no'} scenario=${stateSyncResult.context.scenarioId} status=${stateSyncResult.context.desiredStatus} updated=${stateSyncResult.board.updated ? 'yes' : 'no'} conflict=${stateSyncResult.board.conflict ? 'yes' : 'no'} written=${stateSyncResult.board.written ? 'yes' : 'no'}`
2707
+ );
2708
+ writeInfo(
2709
+ `[pumuki][sdd] state-sync source=${stateSyncResult.context.fromEvidencePath ?? 'default'} board=${stateSyncResult.context.boardPath} entries=${stateSyncResult.board.entries} decision=${stateSyncResult.decision.code}`
2710
+ );
2711
+ if (stateSyncResult.decision.nextAction) {
2712
+ writeInfo(`[pumuki][sdd] state-sync next action: ${stateSyncResult.decision.nextAction}`);
2713
+ }
2714
+ }
2715
+ if (!stateSyncResult.decision.allowed) {
2716
+ return 1;
2717
+ }
2718
+ return 0;
2719
+ }
1983
2720
  return 0;
1984
2721
  }
1985
2722
  case 'adapter': {