pumuki 6.3.26 → 6.3.28
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 +3 -1
- package/bin/pumuki-mcp-enterprise-stdio.js +5 -0
- package/bin/pumuki-mcp-evidence-stdio.js +5 -0
- package/core/gate/conditionMatches.ts +1 -21
- package/core/gate/evaluateGate.js +5 -0
- package/core/gate/evaluateRules.js +5 -0
- package/core/gate/evaluateRules.ts +1 -24
- package/core/gate/scopeMatcher.ts +84 -0
- package/docs/EXECUTION_BOARD.md +749 -376
- package/docs/MCP_SERVERS.md +41 -2
- package/docs/README.md +6 -2
- package/docs/REFRACTOR_PROGRESS.md +374 -6
- package/docs/validation/README.md +11 -1
- package/docs/validation/p9-ruralgo-bug-registry.md +607 -0
- package/docs/validation/p9-ruralgo-fork-validation-tracking.md +904 -0
- package/docs/validation/real-repo-manual-e2e-ruralgo-fork.md +372 -0
- package/integrations/config/skillsCompliance.ts +212 -0
- package/integrations/evidence/integrity.ts +352 -0
- package/integrations/evidence/rulesCoverage.ts +94 -0
- package/integrations/evidence/schema.test.ts +16 -0
- package/integrations/evidence/schema.ts +41 -0
- package/integrations/evidence/writeEvidence.test.ts +68 -0
- package/integrations/evidence/writeEvidence.ts +23 -2
- package/integrations/gate/evaluateAiGate.ts +382 -15
- package/integrations/gate/stagePolicies.ts +70 -15
- package/integrations/gate/waivers.ts +209 -0
- package/integrations/git/findingTraceability.ts +3 -23
- package/integrations/git/index.js +5 -0
- package/integrations/git/runCliCommand.ts +16 -0
- package/integrations/git/runPlatformGate.ts +53 -1
- package/integrations/git/runPlatformGateEvaluation.ts +13 -0
- package/integrations/git/stageRunners.ts +168 -5
- package/integrations/lifecycle/adapter.templates.json +72 -5
- package/integrations/lifecycle/adapter.ts +78 -4
- package/integrations/lifecycle/cli.ts +384 -14
- package/integrations/lifecycle/doctor.ts +534 -0
- package/integrations/lifecycle/hookBlock.ts +2 -1
- package/integrations/lifecycle/index.js +5 -0
- package/integrations/lifecycle/install.ts +115 -3
- package/integrations/lifecycle/openSpecBootstrap.ts +68 -8
- package/integrations/lifecycle/preWriteAutomation.ts +142 -0
- package/integrations/mcp/aiGateCheck.ts +6 -0
- package/integrations/mcp/aiGateReceipt.ts +188 -0
- package/integrations/mcp/enterpriseServer.ts +14 -1
- package/integrations/mcp/enterpriseStdioServer.cli.ts +315 -0
- package/integrations/mcp/evidenceStdioServer.cli.ts +342 -0
- package/integrations/mcp/index.js +5 -0
- package/integrations/sdd/index.js +5 -0
- package/integrations/sdd/index.ts +2 -0
- package/integrations/sdd/policy.ts +191 -2
- package/integrations/sdd/sessionStore.ts +139 -19
- package/integrations/sdd/syncDocs.ts +180 -0
- package/integrations/sdd/types.ts +4 -1
- package/integrations/telemetry/structuredTelemetry.ts +197 -0
- package/package.json +27 -8
- package/scripts/build-p9-validation-manifests.ts +53 -0
- package/scripts/check-p9-ruralgo-baseline-clean.ts +200 -0
- package/scripts/check-p9-ruralgo-baseline-versioned.ts +198 -0
- package/scripts/check-p9-ruralgo-branch-ready.ts +215 -0
- package/scripts/check-p9-ruralgo-install-health.ts +288 -0
- package/scripts/check-p9-ruralgo-runtime-ready.ts +188 -0
- package/scripts/check-package-manifest.ts +49 -0
- package/scripts/check-tracking-single-active.sh +40 -0
- package/scripts/framework-menu-consumer-preflight-lib.ts +31 -0
- package/scripts/framework-menu-consumer-runtime-lib.ts +3 -3
- package/scripts/framework-menu-legacy-audit-lib.ts +35 -7
- package/scripts/framework-menu-matrix-evidence-lib.ts +6 -2
- package/scripts/manage-library.sh +1 -1
- package/scripts/p9-ruralgo-baseline-clean-lib.ts +117 -0
- package/scripts/p9-ruralgo-baseline-versioned-lib.ts +119 -0
- package/scripts/p9-ruralgo-branch-ready-lib.ts +128 -0
- package/scripts/p9-ruralgo-install-health-lib.ts +121 -0
- package/scripts/p9-ruralgo-runtime-ready-lib.ts +149 -0
- package/scripts/p9-validation-manifests-lib.ts +366 -0
- package/scripts/package-manifest-lib.ts +9 -0
- package/skills.lock.json +1 -1
|
@@ -22,11 +22,17 @@ import {
|
|
|
22
22
|
openSddSession,
|
|
23
23
|
readSddStatus,
|
|
24
24
|
refreshSddSession,
|
|
25
|
+
runSddSyncDocs,
|
|
25
26
|
type SddStage,
|
|
27
|
+
type SddSessionState,
|
|
26
28
|
} from '../sdd';
|
|
27
29
|
import { evaluateAiGate } from '../gate/evaluateAiGate';
|
|
28
30
|
import { runEnterpriseAiGateCheck } from '../mcp/aiGateCheck';
|
|
29
31
|
import { emitAuditSummaryNotificationFromAiGate } from '../notifications/emitAuditSummaryNotification';
|
|
32
|
+
import {
|
|
33
|
+
buildPreWriteAutomationTrace,
|
|
34
|
+
type PreWriteAutomationTrace,
|
|
35
|
+
} from './preWriteAutomation';
|
|
30
36
|
import { buildLocalHotspotsReport, type LocalHotspotsReport } from './analyticsHotspots';
|
|
31
37
|
import { resolveHotspotsSaasIngestionAuditPath } from './saasIngestionAudit';
|
|
32
38
|
import { readHotspotsSaasIngestionPayload } from './saasIngestionContract';
|
|
@@ -48,7 +54,7 @@ type LifecycleCommand =
|
|
|
48
54
|
| 'adapter'
|
|
49
55
|
| 'analytics';
|
|
50
56
|
|
|
51
|
-
type SddCommand = 'status' | 'validate' | 'session';
|
|
57
|
+
type SddCommand = 'status' | 'validate' | 'session' | 'sync-docs';
|
|
52
58
|
type LoopCommand = 'run' | 'status' | 'stop' | 'resume' | 'list' | 'export';
|
|
53
59
|
type AnalyticsCommand = 'hotspots';
|
|
54
60
|
type AnalyticsHotspotsCommand = 'report' | 'diagnose';
|
|
@@ -60,6 +66,7 @@ type ParsedArgs = {
|
|
|
60
66
|
purgeArtifacts: boolean;
|
|
61
67
|
updateSpec?: string;
|
|
62
68
|
json: boolean;
|
|
69
|
+
doctorDeep?: boolean;
|
|
63
70
|
sddCommand?: SddCommand;
|
|
64
71
|
loopCommand?: LoopCommand;
|
|
65
72
|
loopSessionId?: string;
|
|
@@ -70,6 +77,7 @@ type ParsedArgs = {
|
|
|
70
77
|
sddSessionAction?: SddSessionAction;
|
|
71
78
|
sddChangeId?: string;
|
|
72
79
|
sddTtlMinutes?: number;
|
|
80
|
+
sddDryRun?: boolean;
|
|
73
81
|
adapterCommand?: 'install';
|
|
74
82
|
adapterAgent?: AdapterAgent;
|
|
75
83
|
adapterDryRun?: boolean;
|
|
@@ -87,7 +95,7 @@ Pumuki lifecycle commands:
|
|
|
87
95
|
pumuki uninstall [--purge-artifacts]
|
|
88
96
|
pumuki remove
|
|
89
97
|
pumuki update [--latest|--spec=<package-spec>]
|
|
90
|
-
pumuki doctor
|
|
98
|
+
pumuki doctor [--deep] [--json]
|
|
91
99
|
pumuki status
|
|
92
100
|
pumuki loop run --objective=<text> [--max-attempts=<n>] [--json]
|
|
93
101
|
pumuki loop status --session=<session-id> [--json]
|
|
@@ -103,6 +111,7 @@ Pumuki lifecycle commands:
|
|
|
103
111
|
pumuki sdd session --open --change=<change-id> [--ttl-minutes=<n>] [--json]
|
|
104
112
|
pumuki sdd session --refresh [--ttl-minutes=<n>] [--json]
|
|
105
113
|
pumuki sdd session --close [--json]
|
|
114
|
+
pumuki sdd sync-docs [--dry-run] [--json]
|
|
106
115
|
`.trim();
|
|
107
116
|
|
|
108
117
|
const LOOP_RUN_POLICY: GatePolicy = {
|
|
@@ -119,6 +128,24 @@ const writeError = (message: string): void => {
|
|
|
119
128
|
process.stderr.write(`${message}\n`);
|
|
120
129
|
};
|
|
121
130
|
|
|
131
|
+
class LifecycleCliExitError extends Error {
|
|
132
|
+
readonly exitCode: number;
|
|
133
|
+
readonly stream: 'stdout' | 'stderr';
|
|
134
|
+
|
|
135
|
+
constructor(
|
|
136
|
+
message: string,
|
|
137
|
+
options: {
|
|
138
|
+
exitCode: number;
|
|
139
|
+
stream: 'stdout' | 'stderr';
|
|
140
|
+
}
|
|
141
|
+
) {
|
|
142
|
+
super(message);
|
|
143
|
+
this.name = 'LifecycleCliExitError';
|
|
144
|
+
this.exitCode = options.exitCode;
|
|
145
|
+
this.stream = options.stream;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
122
149
|
const withOptionalLocation = (message: string, location?: string): string => {
|
|
123
150
|
if (!location || location.trim().length === 0) {
|
|
124
151
|
return message;
|
|
@@ -126,6 +153,20 @@ const withOptionalLocation = (message: string, location?: string): string => {
|
|
|
126
153
|
return `${message} -> ${location}`;
|
|
127
154
|
};
|
|
128
155
|
|
|
156
|
+
const renderSddChangeDescriptor = (session: SddSessionState): string => {
|
|
157
|
+
const canonical = session.changeId?.trim();
|
|
158
|
+
if (!canonical || canonical.length === 0) {
|
|
159
|
+
return 'none';
|
|
160
|
+
}
|
|
161
|
+
const alias = session.changeAlias?.trim();
|
|
162
|
+
if (!alias || alias.length === 0 || alias === canonical) {
|
|
163
|
+
return canonical;
|
|
164
|
+
}
|
|
165
|
+
return `${canonical} alias=${alias}`;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const isHelpFlag = (value: string | undefined): boolean => value === '--help' || value === '-h';
|
|
169
|
+
|
|
129
170
|
const isLifecycleCommand = (value: string): value is LifecycleCommand =>
|
|
130
171
|
value === 'install' ||
|
|
131
172
|
value === 'uninstall' ||
|
|
@@ -379,8 +420,17 @@ const printHotspotsPublishDiagnostics = (diagnostics: HotspotsPublishDiagnostics
|
|
|
379
420
|
|
|
380
421
|
export const parseLifecycleCliArgs = (argv: ReadonlyArray<string>): ParsedArgs => {
|
|
381
422
|
const commandRaw = argv[0];
|
|
382
|
-
if (!commandRaw
|
|
383
|
-
throw new
|
|
423
|
+
if (!commandRaw) {
|
|
424
|
+
throw new LifecycleCliExitError(HELP_TEXT, {
|
|
425
|
+
exitCode: 1,
|
|
426
|
+
stream: 'stderr',
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
if (isHelpFlag(commandRaw)) {
|
|
430
|
+
throw new LifecycleCliExitError(HELP_TEXT, {
|
|
431
|
+
exitCode: 0,
|
|
432
|
+
stream: 'stdout',
|
|
433
|
+
});
|
|
384
434
|
}
|
|
385
435
|
if (!isLifecycleCommand(commandRaw)) {
|
|
386
436
|
throw new Error(`Unknown command "${commandRaw}".\n\n${HELP_TEXT}`);
|
|
@@ -389,6 +439,7 @@ export const parseLifecycleCliArgs = (argv: ReadonlyArray<string>): ParsedArgs =
|
|
|
389
439
|
let purgeArtifacts = false;
|
|
390
440
|
let updateSpec: ParsedArgs['updateSpec'];
|
|
391
441
|
let json = false;
|
|
442
|
+
let doctorDeep = false;
|
|
392
443
|
let sddCommand: ParsedArgs['sddCommand'];
|
|
393
444
|
let loopCommand: ParsedArgs['loopCommand'];
|
|
394
445
|
let loopSessionId: ParsedArgs['loopSessionId'];
|
|
@@ -399,6 +450,7 @@ export const parseLifecycleCliArgs = (argv: ReadonlyArray<string>): ParsedArgs =
|
|
|
399
450
|
let sddSessionAction: ParsedArgs['sddSessionAction'];
|
|
400
451
|
let sddChangeId: ParsedArgs['sddChangeId'];
|
|
401
452
|
let sddTtlMinutes: ParsedArgs['sddTtlMinutes'];
|
|
453
|
+
let sddDryRun = false;
|
|
402
454
|
let adapterCommand: ParsedArgs['adapterCommand'];
|
|
403
455
|
let adapterAgent: ParsedArgs['adapterAgent'];
|
|
404
456
|
let adapterDryRun = false;
|
|
@@ -411,11 +463,23 @@ export const parseLifecycleCliArgs = (argv: ReadonlyArray<string>): ParsedArgs =
|
|
|
411
463
|
|
|
412
464
|
if (commandRaw === 'analytics') {
|
|
413
465
|
const subcommandRaw = argv[1] ?? '';
|
|
466
|
+
if (isHelpFlag(subcommandRaw)) {
|
|
467
|
+
throw new LifecycleCliExitError(HELP_TEXT, {
|
|
468
|
+
exitCode: 0,
|
|
469
|
+
stream: 'stdout',
|
|
470
|
+
});
|
|
471
|
+
}
|
|
414
472
|
if (subcommandRaw !== 'hotspots') {
|
|
415
473
|
throw new Error(`Unsupported analytics command "${subcommandRaw}".\n\n${HELP_TEXT}`);
|
|
416
474
|
}
|
|
417
475
|
analyticsCommand = 'hotspots';
|
|
418
476
|
const hotspotsActionRaw = argv[2] ?? '';
|
|
477
|
+
if (isHelpFlag(hotspotsActionRaw)) {
|
|
478
|
+
throw new LifecycleCliExitError(HELP_TEXT, {
|
|
479
|
+
exitCode: 0,
|
|
480
|
+
stream: 'stdout',
|
|
481
|
+
});
|
|
482
|
+
}
|
|
419
483
|
if (hotspotsActionRaw !== 'report' && hotspotsActionRaw !== 'diagnose') {
|
|
420
484
|
throw new Error(
|
|
421
485
|
`Unsupported analytics hotspots action "${hotspotsActionRaw}".\n\n${HELP_TEXT}`
|
|
@@ -423,6 +487,12 @@ export const parseLifecycleCliArgs = (argv: ReadonlyArray<string>): ParsedArgs =
|
|
|
423
487
|
}
|
|
424
488
|
analyticsHotspotsCommand = hotspotsActionRaw;
|
|
425
489
|
for (const arg of argv.slice(3)) {
|
|
490
|
+
if (isHelpFlag(arg)) {
|
|
491
|
+
throw new LifecycleCliExitError(HELP_TEXT, {
|
|
492
|
+
exitCode: 0,
|
|
493
|
+
stream: 'stdout',
|
|
494
|
+
});
|
|
495
|
+
}
|
|
426
496
|
if (arg === '--json') {
|
|
427
497
|
json = true;
|
|
428
498
|
continue;
|
|
@@ -483,6 +553,12 @@ export const parseLifecycleCliArgs = (argv: ReadonlyArray<string>): ParsedArgs =
|
|
|
483
553
|
|
|
484
554
|
if (commandRaw === 'loop') {
|
|
485
555
|
const subcommandRaw = argv[1] ?? '';
|
|
556
|
+
if (isHelpFlag(subcommandRaw)) {
|
|
557
|
+
throw new LifecycleCliExitError(HELP_TEXT, {
|
|
558
|
+
exitCode: 0,
|
|
559
|
+
stream: 'stdout',
|
|
560
|
+
});
|
|
561
|
+
}
|
|
486
562
|
if (
|
|
487
563
|
subcommandRaw !== 'run' &&
|
|
488
564
|
subcommandRaw !== 'status' &&
|
|
@@ -495,6 +571,12 @@ export const parseLifecycleCliArgs = (argv: ReadonlyArray<string>): ParsedArgs =
|
|
|
495
571
|
}
|
|
496
572
|
loopCommand = subcommandRaw;
|
|
497
573
|
for (const arg of argv.slice(2)) {
|
|
574
|
+
if (isHelpFlag(arg)) {
|
|
575
|
+
throw new LifecycleCliExitError(HELP_TEXT, {
|
|
576
|
+
exitCode: 0,
|
|
577
|
+
stream: 'stdout',
|
|
578
|
+
});
|
|
579
|
+
}
|
|
498
580
|
if (arg === '--json') {
|
|
499
581
|
json = true;
|
|
500
582
|
continue;
|
|
@@ -573,16 +655,29 @@ export const parseLifecycleCliArgs = (argv: ReadonlyArray<string>): ParsedArgs =
|
|
|
573
655
|
|
|
574
656
|
if (commandRaw === 'sdd') {
|
|
575
657
|
const subcommandRaw = argv[1] ?? 'status';
|
|
658
|
+
if (isHelpFlag(subcommandRaw)) {
|
|
659
|
+
throw new LifecycleCliExitError(HELP_TEXT, {
|
|
660
|
+
exitCode: 0,
|
|
661
|
+
stream: 'stdout',
|
|
662
|
+
});
|
|
663
|
+
}
|
|
576
664
|
if (
|
|
577
665
|
subcommandRaw !== 'status' &&
|
|
578
666
|
subcommandRaw !== 'validate' &&
|
|
579
|
-
subcommandRaw !== 'session'
|
|
667
|
+
subcommandRaw !== 'session' &&
|
|
668
|
+
subcommandRaw !== 'sync-docs'
|
|
580
669
|
) {
|
|
581
670
|
throw new Error(`Unsupported SDD subcommand "${subcommandRaw}".\n\n${HELP_TEXT}`);
|
|
582
671
|
}
|
|
583
672
|
sddCommand = subcommandRaw;
|
|
584
673
|
|
|
585
674
|
for (const arg of argv.slice(2)) {
|
|
675
|
+
if (isHelpFlag(arg)) {
|
|
676
|
+
throw new LifecycleCliExitError(HELP_TEXT, {
|
|
677
|
+
exitCode: 0,
|
|
678
|
+
stream: 'stdout',
|
|
679
|
+
});
|
|
680
|
+
}
|
|
586
681
|
if (arg === '--json') {
|
|
587
682
|
json = true;
|
|
588
683
|
continue;
|
|
@@ -615,6 +710,10 @@ export const parseLifecycleCliArgs = (argv: ReadonlyArray<string>): ParsedArgs =
|
|
|
615
710
|
sddTtlMinutes = minutes;
|
|
616
711
|
continue;
|
|
617
712
|
}
|
|
713
|
+
if (arg === '--dry-run') {
|
|
714
|
+
sddDryRun = true;
|
|
715
|
+
continue;
|
|
716
|
+
}
|
|
618
717
|
throw new Error(`Unsupported argument "${arg}".\n\n${HELP_TEXT}`);
|
|
619
718
|
}
|
|
620
719
|
|
|
@@ -635,6 +734,20 @@ export const parseLifecycleCliArgs = (argv: ReadonlyArray<string>): ParsedArgs =
|
|
|
635
734
|
sddStage: sddStage ?? 'PRE_COMMIT',
|
|
636
735
|
};
|
|
637
736
|
}
|
|
737
|
+
if (sddCommand === 'sync-docs') {
|
|
738
|
+
if (sddStage || sddSessionAction || sddChangeId || typeof sddTtlMinutes === 'number') {
|
|
739
|
+
throw new Error(
|
|
740
|
+
`"pumuki sdd sync-docs" only supports [--dry-run] [--json].\n\n${HELP_TEXT}`
|
|
741
|
+
);
|
|
742
|
+
}
|
|
743
|
+
return {
|
|
744
|
+
command: commandRaw,
|
|
745
|
+
purgeArtifacts: false,
|
|
746
|
+
json,
|
|
747
|
+
sddCommand,
|
|
748
|
+
sddDryRun,
|
|
749
|
+
};
|
|
750
|
+
}
|
|
638
751
|
|
|
639
752
|
if (!sddSessionAction) {
|
|
640
753
|
throw new Error(
|
|
@@ -660,12 +773,24 @@ export const parseLifecycleCliArgs = (argv: ReadonlyArray<string>): ParsedArgs =
|
|
|
660
773
|
|
|
661
774
|
if (commandRaw === 'adapter') {
|
|
662
775
|
const subcommandRaw = argv[1] ?? '';
|
|
776
|
+
if (isHelpFlag(subcommandRaw)) {
|
|
777
|
+
throw new LifecycleCliExitError(HELP_TEXT, {
|
|
778
|
+
exitCode: 0,
|
|
779
|
+
stream: 'stdout',
|
|
780
|
+
});
|
|
781
|
+
}
|
|
663
782
|
if (subcommandRaw !== 'install') {
|
|
664
783
|
throw new Error(`Unsupported adapter subcommand "${subcommandRaw}".\n\n${HELP_TEXT}`);
|
|
665
784
|
}
|
|
666
785
|
adapterCommand = 'install';
|
|
667
786
|
|
|
668
787
|
for (const arg of argv.slice(2)) {
|
|
788
|
+
if (isHelpFlag(arg)) {
|
|
789
|
+
throw new LifecycleCliExitError(HELP_TEXT, {
|
|
790
|
+
exitCode: 0,
|
|
791
|
+
stream: 'stdout',
|
|
792
|
+
});
|
|
793
|
+
}
|
|
669
794
|
if (arg === '--json') {
|
|
670
795
|
json = true;
|
|
671
796
|
continue;
|
|
@@ -694,6 +819,12 @@ export const parseLifecycleCliArgs = (argv: ReadonlyArray<string>): ParsedArgs =
|
|
|
694
819
|
}
|
|
695
820
|
|
|
696
821
|
for (const arg of argv.slice(1)) {
|
|
822
|
+
if (isHelpFlag(arg)) {
|
|
823
|
+
throw new LifecycleCliExitError(HELP_TEXT, {
|
|
824
|
+
exitCode: 0,
|
|
825
|
+
stream: 'stdout',
|
|
826
|
+
});
|
|
827
|
+
}
|
|
697
828
|
if (arg === '--json') {
|
|
698
829
|
json = true;
|
|
699
830
|
continue;
|
|
@@ -710,16 +841,27 @@ export const parseLifecycleCliArgs = (argv: ReadonlyArray<string>): ParsedArgs =
|
|
|
710
841
|
updateSpec = arg.slice('--spec='.length).trim();
|
|
711
842
|
continue;
|
|
712
843
|
}
|
|
844
|
+
if (arg === '--deep') {
|
|
845
|
+
if (commandRaw !== 'doctor') {
|
|
846
|
+
throw new Error(`Unsupported argument "${arg}".\n\n${HELP_TEXT}`);
|
|
847
|
+
}
|
|
848
|
+
doctorDeep = true;
|
|
849
|
+
continue;
|
|
850
|
+
}
|
|
713
851
|
|
|
714
852
|
throw new Error(`Unsupported argument "${arg}".\n\n${HELP_TEXT}`);
|
|
715
853
|
}
|
|
716
854
|
|
|
717
|
-
|
|
855
|
+
const parsedArgs: ParsedArgs = {
|
|
718
856
|
command: commandRaw,
|
|
719
857
|
purgeArtifacts,
|
|
720
858
|
updateSpec,
|
|
721
859
|
json,
|
|
722
860
|
};
|
|
861
|
+
if (doctorDeep) {
|
|
862
|
+
parsedArgs.doctorDeep = true;
|
|
863
|
+
}
|
|
864
|
+
return parsedArgs;
|
|
723
865
|
};
|
|
724
866
|
|
|
725
867
|
const printDoctorReport = (report: LifecycleDoctorReport): void => {
|
|
@@ -734,6 +876,14 @@ const printDoctorReport = (report: LifecycleDoctorReport): void => {
|
|
|
734
876
|
writeInfo(
|
|
735
877
|
`[pumuki] hook pre-push: ${report.hookStatus['pre-push'].managedBlockPresent ? 'managed' : 'missing'}`
|
|
736
878
|
);
|
|
879
|
+
if (report.deep?.enabled) {
|
|
880
|
+
writeInfo(`[pumuki][deep] checks: ${report.deep.checks.length}`);
|
|
881
|
+
for (const check of report.deep.checks) {
|
|
882
|
+
writeInfo(
|
|
883
|
+
`[pumuki][deep] ${check.id}: ${check.status.toUpperCase()} - ${check.message}`
|
|
884
|
+
);
|
|
885
|
+
}
|
|
886
|
+
}
|
|
737
887
|
|
|
738
888
|
if (report.issues.length === 0) {
|
|
739
889
|
writeInfo('[pumuki] doctor verdict: PASS');
|
|
@@ -752,6 +902,14 @@ const PRE_WRITE_TELEMETRY_CHAIN = 'pumuki->mcp->ai_gate->ai_evidence';
|
|
|
752
902
|
type PreWriteValidationEnvelope = {
|
|
753
903
|
sdd: ReturnType<typeof evaluateSddPolicy>;
|
|
754
904
|
ai_gate: ReturnType<typeof evaluateAiGate>;
|
|
905
|
+
automation: {
|
|
906
|
+
attempted: boolean;
|
|
907
|
+
actions: Array<{
|
|
908
|
+
action: 'refresh_evidence' | 'refresh_mcp_receipt';
|
|
909
|
+
status: 'OK' | 'FAILED';
|
|
910
|
+
details: string;
|
|
911
|
+
}>;
|
|
912
|
+
};
|
|
755
913
|
telemetry: {
|
|
756
914
|
chain: typeof PRE_WRITE_TELEMETRY_CHAIN;
|
|
757
915
|
stage: SddStage;
|
|
@@ -769,6 +927,131 @@ const defaultLifecycleCliDependencies: LifecycleCliDependencies = {
|
|
|
769
927
|
runPlatformGate,
|
|
770
928
|
};
|
|
771
929
|
|
|
930
|
+
const PRE_WRITE_HINTS_BY_CODE: Readonly<Record<string, string>> = {
|
|
931
|
+
EVIDENCE_MISSING: 'Regenera evidencia ejecutando una auditoría completa antes de continuar.',
|
|
932
|
+
EVIDENCE_INVALID: 'Corrige el contrato de .ai_evidence.json y vuelve a ejecutar el gate.',
|
|
933
|
+
EVIDENCE_INTEGRITY_MISSING: 'Regenera evidencia para crear la metadata de integridad criptográfica.',
|
|
934
|
+
EVIDENCE_INTEGRITY_UNAVAILABLE: 'Regenera evidencia desde una auditoría válida para restaurar integridad.',
|
|
935
|
+
EVIDENCE_INTEGRITY_SCHEMA_INVALID: 'Regenera evidencia; el bloque de integridad no cumple el contrato.',
|
|
936
|
+
EVIDENCE_INTEGRITY_HASH_FORMAT_INVALID: 'Regenera evidencia; los hashes de integridad son inválidos.',
|
|
937
|
+
EVIDENCE_INTEGRITY_PREVIOUS_CHAIN_HASH_INVALID:
|
|
938
|
+
'Regenera evidencia para reparar previous_chain_hash.',
|
|
939
|
+
EVIDENCE_INTEGRITY_TIMESTAMP_MISMATCH: 'Regenera evidencia para alinear timestamp e integridad.',
|
|
940
|
+
EVIDENCE_INTEGRITY_PAYLOAD_HASH_MISMATCH: 'Regenera evidencia; el payload hash no coincide.',
|
|
941
|
+
EVIDENCE_INTEGRITY_CHAIN_HASH_MISMATCH: 'Regenera evidencia; la cadena criptográfica no coincide.',
|
|
942
|
+
EVIDENCE_INTEGRITY_SIGNATURE_REQUIRED: 'Configura firma o desactiva exigencia de firma explícita.',
|
|
943
|
+
EVIDENCE_INTEGRITY_SIGNATURE_FORMAT_INVALID:
|
|
944
|
+
'Regenera evidencia o corrige metadatos de firma (algorithm/key_id/value).',
|
|
945
|
+
EVIDENCE_INTEGRITY_SIGNATURE_KEY_MISSING:
|
|
946
|
+
'Configura PUMUKI_EVIDENCE_SIGNING_KEY para verificar firma localmente.',
|
|
947
|
+
EVIDENCE_INTEGRITY_SIGNATURE_KEY_MISMATCH:
|
|
948
|
+
'Alinea PUMUKI_EVIDENCE_SIGNING_KEY_ID con el key_id usado para firmar la evidencia.',
|
|
949
|
+
EVIDENCE_INTEGRITY_SIGNATURE_INVALID: 'Regenera evidencia firmada; la firma actual no verifica.',
|
|
950
|
+
EVIDENCE_STALE: 'Refresca evidencia para este repo y rama.',
|
|
951
|
+
EVIDENCE_REPO_ROOT_MISMATCH: 'Regenera evidencia desde este repositorio.',
|
|
952
|
+
EVIDENCE_BRANCH_MISMATCH: 'Regenera evidencia en la rama actual.',
|
|
953
|
+
EVIDENCE_RULES_COVERAGE_MISSING: 'Ejecuta auditoría completa para recalcular rules_coverage.',
|
|
954
|
+
EVIDENCE_RULES_COVERAGE_INCOMPLETE: 'Asegura unevaluated=0 y coverage_ratio=1.',
|
|
955
|
+
GITFLOW_PROTECTED_BRANCH: 'Trabaja en feature/* y evita ramas protegidas.',
|
|
956
|
+
MCP_ENTERPRISE_RECEIPT_MISSING: 'Invoca ai_gate_check desde pumuki-enterprise MCP antes de PRE_WRITE.',
|
|
957
|
+
MCP_ENTERPRISE_RECEIPT_INVALID: 'Corrige recibo MCP y vuelve a invocar ai_gate_check.',
|
|
958
|
+
MCP_ENTERPRISE_RECEIPT_STALE: 'Vuelve a ejecutar ai_gate_check para emitir recibo fresco.',
|
|
959
|
+
MCP_ENTERPRISE_RECEIPT_STAGE_MISMATCH: 'Reejecuta ai_gate_check con stage PRE_WRITE.',
|
|
960
|
+
MCP_ENTERPRISE_RECEIPT_REPO_ROOT_MISMATCH: 'Genera el recibo MCP en este mismo repositorio.',
|
|
961
|
+
WAIVER_POLICY_INVALID: 'Corrige el archivo de waivers y vuelve a ejecutar la validación.',
|
|
962
|
+
};
|
|
963
|
+
|
|
964
|
+
const wrapPreWritePanelLine = (value: string, width: number): string[] => {
|
|
965
|
+
if (width < 20 || value.length <= width) {
|
|
966
|
+
return [value];
|
|
967
|
+
}
|
|
968
|
+
const words = value.split(/\s+/);
|
|
969
|
+
const lines: string[] = [];
|
|
970
|
+
let current = '';
|
|
971
|
+
for (const word of words) {
|
|
972
|
+
if (current.length === 0) {
|
|
973
|
+
current = word;
|
|
974
|
+
continue;
|
|
975
|
+
}
|
|
976
|
+
if (`${current} ${word}`.length <= width) {
|
|
977
|
+
current = `${current} ${word}`;
|
|
978
|
+
continue;
|
|
979
|
+
}
|
|
980
|
+
lines.push(current);
|
|
981
|
+
current = word;
|
|
982
|
+
}
|
|
983
|
+
if (current.length > 0) {
|
|
984
|
+
lines.push(current);
|
|
985
|
+
}
|
|
986
|
+
return lines;
|
|
987
|
+
};
|
|
988
|
+
|
|
989
|
+
const renderPreWritePanel = (lines: ReadonlyArray<string>): string => {
|
|
990
|
+
const terminalWidth = Number.isFinite(process.stdout.columns ?? NaN)
|
|
991
|
+
? Number(process.stdout.columns)
|
|
992
|
+
: 110;
|
|
993
|
+
const width = Math.min(140, Math.max(86, terminalWidth - 2));
|
|
994
|
+
const innerWidth = width - 4;
|
|
995
|
+
const normalized = lines.flatMap((line) => wrapPreWritePanelLine(line, innerWidth));
|
|
996
|
+
const top = `╔${'═'.repeat(width - 2)}╗`;
|
|
997
|
+
const bottom = `╚${'═'.repeat(width - 2)}╝`;
|
|
998
|
+
const body = normalized.map((line) => `║ ${line.padEnd(innerWidth, ' ')} ║`);
|
|
999
|
+
return [top, ...body, bottom].join('\n');
|
|
1000
|
+
};
|
|
1001
|
+
|
|
1002
|
+
const buildPreWriteValidationPanel = (params: {
|
|
1003
|
+
sdd: ReturnType<typeof evaluateSddPolicy>;
|
|
1004
|
+
aiGate: ReturnType<typeof evaluateAiGate>;
|
|
1005
|
+
automation: PreWriteAutomationTrace;
|
|
1006
|
+
}): string => {
|
|
1007
|
+
const git = params.aiGate.repo_state.git;
|
|
1008
|
+
const receipt = params.aiGate.mcp_receipt;
|
|
1009
|
+
const policyTrace = params.aiGate.policy.trace;
|
|
1010
|
+
const lines: string[] = [
|
|
1011
|
+
'PRE-FLIGHT CHECK',
|
|
1012
|
+
`Stage: ${params.sdd.stage} · SDD: ${params.sdd.decision.code} · AI Gate: ${params.aiGate.status}`,
|
|
1013
|
+
`Branch: ${git.branch ?? 'unknown'} · Upstream: ${git.upstream ?? 'none'}`,
|
|
1014
|
+
`Worktree: dirty=${git.dirty ? 'yes' : 'no'} staged=${git.staged} unstaged=${git.unstaged} ahead=${git.ahead} behind=${git.behind}`,
|
|
1015
|
+
`Policy: source=${policyTrace.source} bundle=${policyTrace.bundle} hash=${policyTrace.hash}`,
|
|
1016
|
+
`Policy signature: version=${policyTrace.version ?? 'n/a'} signature=${policyTrace.signature ?? 'n/a'}`,
|
|
1017
|
+
`Evidence: kind=${params.aiGate.evidence.kind} age=${params.aiGate.evidence.age_seconds ?? 'n/a'}s max=${params.aiGate.evidence.max_age_seconds}s`,
|
|
1018
|
+
`Evidence source: ${params.aiGate.evidence.source} path=${params.aiGate.evidence.path}`,
|
|
1019
|
+
`Evidence digest: ${params.aiGate.evidence.digest ?? 'n/a'} generated_at=${params.aiGate.evidence.generated_at ?? 'n/a'}`,
|
|
1020
|
+
`Evidence integrity: status=${params.aiGate.evidence.integrity.status} chain_hash=${params.aiGate.evidence.integrity.chain_hash ?? 'n/a'} prev=${params.aiGate.evidence.integrity.previous_chain_hash ?? 'n/a'} signature=${params.aiGate.evidence.integrity.signature_present ? 'present' : 'absent'} verified=${params.aiGate.evidence.integrity.signature_verified === null ? 'n/a' : params.aiGate.evidence.integrity.signature_verified ? 'yes' : 'no'}`,
|
|
1021
|
+
`Waivers: status=${params.aiGate.waivers.status} applied=${params.aiGate.waivers.applied.length} path=${params.aiGate.waivers.path}`,
|
|
1022
|
+
`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`,
|
|
1023
|
+
`Auto-heal: attempted=${params.automation.attempted ? 'yes' : 'no'} actions=${params.automation.actions.length}`,
|
|
1024
|
+
`Violations: ${params.aiGate.violations.length}`,
|
|
1025
|
+
];
|
|
1026
|
+
|
|
1027
|
+
if (params.automation.actions.length > 0) {
|
|
1028
|
+
lines.push('');
|
|
1029
|
+
lines.push('Auto-heal actions:');
|
|
1030
|
+
for (const action of params.automation.actions) {
|
|
1031
|
+
lines.push(`- ${action.action}: ${action.status} (${action.details})`);
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
if (params.aiGate.violations.length > 0) {
|
|
1036
|
+
lines.push('');
|
|
1037
|
+
lines.push('Blocking causes:');
|
|
1038
|
+
for (const violation of params.aiGate.violations) {
|
|
1039
|
+
lines.push(`- ${violation.code}: ${violation.message}`);
|
|
1040
|
+
}
|
|
1041
|
+
lines.push('');
|
|
1042
|
+
lines.push('Operational hints:');
|
|
1043
|
+
for (const violation of params.aiGate.violations) {
|
|
1044
|
+
const hint = PRE_WRITE_HINTS_BY_CODE[violation.code];
|
|
1045
|
+
if (!hint) {
|
|
1046
|
+
continue;
|
|
1047
|
+
}
|
|
1048
|
+
lines.push(`- ${violation.code}: ${hint}`);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
return renderPreWritePanel(lines);
|
|
1053
|
+
};
|
|
1054
|
+
|
|
772
1055
|
const resolveSddDecisionLocation = (
|
|
773
1056
|
result: ReturnType<typeof evaluateSddPolicy>
|
|
774
1057
|
) => {
|
|
@@ -779,8 +1062,10 @@ const resolveSddDecisionLocation = (
|
|
|
779
1062
|
case 'OPENSPEC_PROJECT_MISSING':
|
|
780
1063
|
case 'SDD_VALIDATION_FAILED':
|
|
781
1064
|
case 'SDD_VALIDATION_ERROR':
|
|
1065
|
+
case 'SDD_VALIDATION_EMPTY':
|
|
782
1066
|
return 'openspec/changes:1';
|
|
783
1067
|
case 'SDD_CHANGE_MISSING':
|
|
1068
|
+
case 'SDD_CHANGE_INCOMPLETE':
|
|
784
1069
|
return changeId && changeId.trim().length > 0
|
|
785
1070
|
? `openspec/changes/${changeId.trim()}:1`
|
|
786
1071
|
: 'openspec/changes:1';
|
|
@@ -808,10 +1093,15 @@ const resolveAiGateViolationLocation = (code: string) => {
|
|
|
808
1093
|
|
|
809
1094
|
const buildPreWriteValidationEnvelope = (
|
|
810
1095
|
result: ReturnType<typeof evaluateSddPolicy>,
|
|
811
|
-
aiGate: ReturnType<typeof evaluateAiGate
|
|
1096
|
+
aiGate: ReturnType<typeof evaluateAiGate>,
|
|
1097
|
+
automation: PreWriteAutomationTrace
|
|
812
1098
|
): PreWriteValidationEnvelope => ({
|
|
813
1099
|
sdd: result,
|
|
814
1100
|
ai_gate: aiGate,
|
|
1101
|
+
automation: {
|
|
1102
|
+
attempted: automation.attempted,
|
|
1103
|
+
actions: [...automation.actions],
|
|
1104
|
+
},
|
|
815
1105
|
telemetry: {
|
|
816
1106
|
chain: PRE_WRITE_TELEMETRY_CHAIN,
|
|
817
1107
|
stage: result.stage,
|
|
@@ -866,6 +1156,31 @@ export const runLifecycleCli = async (
|
|
|
866
1156
|
if (result.openSpecBootstrap.skippedReason === 'NO_PACKAGE_JSON') {
|
|
867
1157
|
writeInfo('[pumuki] openspec bootstrap skipped npm install (package.json not found)');
|
|
868
1158
|
}
|
|
1159
|
+
if (result.openSpecBootstrap.skippedReason === 'NPM_INSTALL_FAILED') {
|
|
1160
|
+
writeInfo('[pumuki] openspec bootstrap npm install failed; continuing in standalone mode (hooks installed)');
|
|
1161
|
+
if (result.openSpecBootstrap.skippedDetails) {
|
|
1162
|
+
writeInfo(`[pumuki] openspec bootstrap detail: ${result.openSpecBootstrap.skippedDetails}`);
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
if (result.openSpecBootstrap.skippedReason === 'ENGINE_MISMATCH') {
|
|
1166
|
+
writeInfo('[pumuki] openspec bootstrap skipped npm install due to repository engines mismatch; continuing in standalone mode (hooks installed)');
|
|
1167
|
+
if (result.openSpecBootstrap.skippedDetails) {
|
|
1168
|
+
writeInfo(`[pumuki] openspec bootstrap detail: ${result.openSpecBootstrap.skippedDetails}`);
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
const consumerBootstrap = result.consumerPackageBootstrap;
|
|
1173
|
+
if (consumerBootstrap.packageInstalled) {
|
|
1174
|
+
writeInfo(
|
|
1175
|
+
`[pumuki] consumer package bootstrap: installed=yes source=${consumerBootstrap.dependencySource ?? 'devDependencies'} spec=${consumerBootstrap.targetSpec ?? 'n/a'}`
|
|
1176
|
+
);
|
|
1177
|
+
} else if (consumerBootstrap.skippedReason) {
|
|
1178
|
+
writeInfo(
|
|
1179
|
+
`[pumuki] consumer package bootstrap: installed=no reason=${consumerBootstrap.skippedReason.toLowerCase()}`
|
|
1180
|
+
);
|
|
1181
|
+
if (consumerBootstrap.skippedDetails) {
|
|
1182
|
+
writeInfo(`[pumuki] consumer package bootstrap detail: ${consumerBootstrap.skippedDetails}`);
|
|
1183
|
+
}
|
|
869
1184
|
}
|
|
870
1185
|
return 0;
|
|
871
1186
|
}
|
|
@@ -906,8 +1221,14 @@ export const runLifecycleCli = async (
|
|
|
906
1221
|
return 0;
|
|
907
1222
|
}
|
|
908
1223
|
case 'doctor': {
|
|
909
|
-
const report = runLifecycleDoctor(
|
|
910
|
-
|
|
1224
|
+
const report = runLifecycleDoctor({
|
|
1225
|
+
deep: parsed.doctorDeep,
|
|
1226
|
+
});
|
|
1227
|
+
if (parsed.json) {
|
|
1228
|
+
writeInfo(JSON.stringify(report, null, 2));
|
|
1229
|
+
} else {
|
|
1230
|
+
printDoctorReport(report);
|
|
1231
|
+
}
|
|
911
1232
|
return report.issues.some((issue) => issue.severity === 'error') ? 1 : 0;
|
|
912
1233
|
}
|
|
913
1234
|
case 'status': {
|
|
@@ -1150,6 +1471,26 @@ export const runLifecycleCli = async (
|
|
|
1150
1471
|
return 1;
|
|
1151
1472
|
}
|
|
1152
1473
|
case 'sdd': {
|
|
1474
|
+
if (parsed.sddCommand === 'sync-docs') {
|
|
1475
|
+
const syncResult = runSddSyncDocs({
|
|
1476
|
+
repoRoot: process.cwd(),
|
|
1477
|
+
dryRun: parsed.sddDryRun,
|
|
1478
|
+
});
|
|
1479
|
+
if (parsed.json) {
|
|
1480
|
+
writeInfo(JSON.stringify(syncResult, null, 2));
|
|
1481
|
+
} else {
|
|
1482
|
+
writeInfo(
|
|
1483
|
+
`[pumuki][sdd] sync-docs: change=${syncResult.changeId} dry-run=${syncResult.dryRun ? 'yes' : 'no'} changed=${syncResult.changed ? 'yes' : 'no'} file=${syncResult.targetPathRelative}`
|
|
1484
|
+
);
|
|
1485
|
+
if (syncResult.diffPreview.length > 0) {
|
|
1486
|
+
writeInfo('[pumuki][sdd] sync-docs diff preview:');
|
|
1487
|
+
writeInfo(syncResult.diffPreview);
|
|
1488
|
+
} else {
|
|
1489
|
+
writeInfo('[pumuki][sdd] sync-docs diff preview: no changes.');
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
return 0;
|
|
1493
|
+
}
|
|
1153
1494
|
if (parsed.sddCommand === 'status') {
|
|
1154
1495
|
const sddStatus = readSddStatus();
|
|
1155
1496
|
if (parsed.json) {
|
|
@@ -1166,7 +1507,7 @@ export const runLifecycleCli = async (
|
|
|
1166
1507
|
`[pumuki][sdd] openspec project initialized: ${sddStatus.openspec.projectInitialized ? 'yes' : 'no'}`
|
|
1167
1508
|
);
|
|
1168
1509
|
writeInfo(
|
|
1169
|
-
`[pumuki][sdd] session: active=${sddStatus.session.active ? 'yes' : 'no'} valid=${sddStatus.session.valid ? 'yes' : 'no'} change=${sddStatus.session
|
|
1510
|
+
`[pumuki][sdd] session: active=${sddStatus.session.active ? 'yes' : 'no'} valid=${sddStatus.session.valid ? 'yes' : 'no'} change=${renderSddChangeDescriptor(sddStatus.session)}`
|
|
1170
1511
|
);
|
|
1171
1512
|
if (typeof sddStatus.session.remainingSeconds === 'number') {
|
|
1172
1513
|
writeInfo(
|
|
@@ -1181,17 +1522,33 @@ export const runLifecycleCli = async (
|
|
|
1181
1522
|
stage: parsed.sddStage ?? 'PRE_COMMIT',
|
|
1182
1523
|
});
|
|
1183
1524
|
const shouldEvaluateAiGate = result.stage === 'PRE_WRITE';
|
|
1184
|
-
|
|
1525
|
+
let aiGate = shouldEvaluateAiGate
|
|
1185
1526
|
? runEnterpriseAiGateCheck({
|
|
1186
1527
|
repoRoot: process.cwd(),
|
|
1187
1528
|
stage: result.stage,
|
|
1529
|
+
requireMcpReceipt: true,
|
|
1188
1530
|
}).result
|
|
1189
1531
|
: null;
|
|
1532
|
+
const automationTrace: PreWriteAutomationTrace = {
|
|
1533
|
+
attempted: false,
|
|
1534
|
+
actions: [],
|
|
1535
|
+
};
|
|
1536
|
+
if (result.stage === 'PRE_WRITE' && aiGate) {
|
|
1537
|
+
const auto = await buildPreWriteAutomationTrace({
|
|
1538
|
+
repoRoot: process.cwd(),
|
|
1539
|
+
sdd: result,
|
|
1540
|
+
aiGate,
|
|
1541
|
+
runPlatformGate: activeDependencies.runPlatformGate,
|
|
1542
|
+
});
|
|
1543
|
+
aiGate = auto.aiGate;
|
|
1544
|
+
automationTrace.attempted = auto.trace.attempted;
|
|
1545
|
+
automationTrace.actions = auto.trace.actions;
|
|
1546
|
+
}
|
|
1190
1547
|
if (parsed.json) {
|
|
1191
1548
|
writeInfo(
|
|
1192
1549
|
JSON.stringify(
|
|
1193
1550
|
aiGate
|
|
1194
|
-
? buildPreWriteValidationEnvelope(result, aiGate)
|
|
1551
|
+
? buildPreWriteValidationEnvelope(result, aiGate, automationTrace)
|
|
1195
1552
|
: result,
|
|
1196
1553
|
null,
|
|
1197
1554
|
2
|
|
@@ -1213,6 +1570,11 @@ export const runLifecycleCli = async (
|
|
|
1213
1570
|
);
|
|
1214
1571
|
}
|
|
1215
1572
|
if (aiGate) {
|
|
1573
|
+
writeInfo(buildPreWriteValidationPanel({
|
|
1574
|
+
sdd: result,
|
|
1575
|
+
aiGate,
|
|
1576
|
+
automation: automationTrace,
|
|
1577
|
+
}));
|
|
1216
1578
|
writeInfo(
|
|
1217
1579
|
`[pumuki][ai-gate] stage=${aiGate.stage} status=${aiGate.status} violations=${aiGate.violations.length}`
|
|
1218
1580
|
);
|
|
@@ -1251,7 +1613,7 @@ export const runLifecycleCli = async (
|
|
|
1251
1613
|
writeInfo(JSON.stringify(session, null, 2));
|
|
1252
1614
|
} else {
|
|
1253
1615
|
writeInfo(
|
|
1254
|
-
`[pumuki][sdd] session opened: change=${session
|
|
1616
|
+
`[pumuki][sdd] session opened: change=${renderSddChangeDescriptor(session)} ttlMinutes=${session.ttlMinutes ?? 'unknown'} valid=${session.valid ? 'yes' : 'no'}`
|
|
1255
1617
|
);
|
|
1256
1618
|
}
|
|
1257
1619
|
return 0;
|
|
@@ -1264,7 +1626,7 @@ export const runLifecycleCli = async (
|
|
|
1264
1626
|
writeInfo(JSON.stringify(session, null, 2));
|
|
1265
1627
|
} else {
|
|
1266
1628
|
writeInfo(
|
|
1267
|
-
`[pumuki][sdd] session refreshed: change=${session
|
|
1629
|
+
`[pumuki][sdd] session refreshed: change=${renderSddChangeDescriptor(session)} ttlMinutes=${session.ttlMinutes ?? 'unknown'} valid=${session.valid ? 'yes' : 'no'}`
|
|
1268
1630
|
);
|
|
1269
1631
|
}
|
|
1270
1632
|
return 0;
|
|
@@ -1305,6 +1667,14 @@ export const runLifecycleCli = async (
|
|
|
1305
1667
|
return 1;
|
|
1306
1668
|
}
|
|
1307
1669
|
} catch (error) {
|
|
1670
|
+
if (error instanceof LifecycleCliExitError) {
|
|
1671
|
+
if (error.stream === 'stdout') {
|
|
1672
|
+
writeInfo(error.message);
|
|
1673
|
+
} else {
|
|
1674
|
+
writeError(error.message);
|
|
1675
|
+
}
|
|
1676
|
+
return error.exitCode;
|
|
1677
|
+
}
|
|
1308
1678
|
const message = error instanceof Error ? error.message : 'Unexpected lifecycle CLI error.';
|
|
1309
1679
|
writeError(message);
|
|
1310
1680
|
return 1;
|