pumuki 6.3.113 → 6.3.115
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/CHANGELOG.md +52 -5
- package/README.md +4 -2
- package/VERSION +1 -1
- package/core/facts/detectors/typescript/index.test.ts +0 -229
- package/core/facts/detectors/typescript/index.ts +0 -278
- package/core/facts/extractHeuristicFacts.ts +0 -4
- package/core/rules/presets/heuristics/typescript.test.ts +1 -21
- package/core/rules/presets/heuristics/typescript.ts +0 -72
- package/docs/README.md +13 -9
- package/docs/codex-skills/backend-enterprise-rules.md +3 -3
- package/docs/operations/RELEASE_NOTES.md +41 -4
- package/docs/product/API_REFERENCE.md +1 -1
- package/docs/product/HOW_IT_WORKS.md +6 -0
- package/docs/product/INSTALLATION.md +1 -1
- package/docs/product/USAGE.md +42 -5
- package/docs/tracking/plan-curso-pumuki-stack-my-architecture.md +100 -44
- package/docs/validation/README.md +6 -3
- package/integrations/config/skillsDetectorRegistry.ts +0 -24
- package/integrations/config/skillsMarkdownRules.ts +0 -57
- package/integrations/evidence/buildEvidence.ts +0 -24
- package/integrations/evidence/repoState.ts +9 -7
- package/integrations/evidence/schema.ts +0 -18
- package/integrations/evidence/writeEvidence.ts +0 -24
- package/integrations/gate/evaluateAiGate.ts +8 -251
- package/integrations/gate/remediationCatalog.ts +0 -8
- package/integrations/git/GitService.ts +44 -5
- package/integrations/git/aiGateRepoPolicyFindings.ts +86 -17
- package/integrations/git/runPlatformGate.ts +1 -9
- package/integrations/git/runPlatformGateFacts.ts +19 -1
- package/integrations/git/runPlatformGateOutput.ts +41 -42
- package/integrations/lifecycle/adapter.templates.json +1 -0
- package/integrations/lifecycle/adapter.ts +0 -24
- package/integrations/lifecycle/audit.ts +101 -0
- package/integrations/lifecycle/cli.ts +120 -99
- package/integrations/lifecycle/cliSdd.ts +4 -26
- package/integrations/lifecycle/doctor.ts +40 -102
- package/integrations/lifecycle/index.ts +2 -0
- package/integrations/lifecycle/install.ts +0 -21
- package/integrations/lifecycle/packageInfo.ts +1 -118
- package/integrations/lifecycle/state.ts +1 -8
- package/integrations/lifecycle/status.ts +40 -59
- package/integrations/lifecycle/watch.ts +1 -1
- package/integrations/mcp/aiGateCheck.ts +10 -194
- package/integrations/mcp/autoExecuteAiStart.ts +116 -92
- package/integrations/mcp/enterpriseServer.ts +7 -23
- package/integrations/mcp/enterpriseStdioServer.cli.ts +4 -31
- package/integrations/mcp/preFlightCheck.ts +5 -67
- package/integrations/platform/detectPlatforms.ts +37 -0
- package/integrations/sdd/policy.ts +28 -20
- package/package.json +1 -1
- package/scripts/check-tracking-single-active.sh +1 -1
- package/scripts/consumer-menu-matrix-baseline-report-lib.ts +13 -38
- package/scripts/consumer-postinstall-resolve-args.cjs +44 -0
- package/scripts/consumer-postinstall.cjs +76 -21
- package/scripts/framework-menu-advanced-view-lib.ts +0 -49
- package/scripts/framework-menu-consumer-actions-lib.ts +28 -4
- package/scripts/framework-menu-consumer-preflight-hints.ts +5 -2
- package/scripts/framework-menu-consumer-preflight-render.ts +0 -10
- package/scripts/framework-menu-consumer-preflight-run.ts +0 -23
- package/scripts/framework-menu-consumer-preflight-types.ts +0 -12
- package/scripts/framework-menu-consumer-runtime-actions.ts +87 -17
- package/scripts/framework-menu-consumer-runtime-audit.ts +36 -2
- package/scripts/framework-menu-consumer-runtime-evidence-classic.ts +140 -0
- package/scripts/framework-menu-consumer-runtime-lib.ts +2 -38
- package/scripts/framework-menu-consumer-runtime-menu.ts +4 -31
- package/scripts/framework-menu-consumer-runtime-types.ts +3 -5
- package/scripts/framework-menu-evidence-summary-lib.ts +1 -0
- package/scripts/framework-menu-evidence-summary-read.ts +57 -5
- package/scripts/framework-menu-evidence-summary-severity.ts +3 -1
- package/scripts/framework-menu-evidence-summary-types.ts +7 -0
- package/scripts/framework-menu-gate-lib.ts +9 -0
- package/scripts/framework-menu-layout-data.ts +5 -0
- package/scripts/framework-menu-matrix-baseline-lib.ts +15 -14
- package/scripts/framework-menu-matrix-canary-lib.ts +22 -1
- package/scripts/framework-menu-matrix-evidence-lib.ts +1 -0
- package/scripts/framework-menu-matrix-evidence-types.ts +13 -1
- package/scripts/framework-menu-matrix-runner-lib.ts +35 -0
- package/scripts/framework-menu-system-notifications-cause.ts +0 -3
- package/scripts/framework-menu-system-notifications-macos-swift-source.ts +24 -204
- package/scripts/framework-menu-system-notifications-macos.ts +4 -0
- package/scripts/framework-menu-system-notifications-payloads-blocked.ts +1 -1
- package/scripts/framework-menu-system-notifications-text.ts +1 -7
- package/scripts/framework-menu.ts +3 -24
- package/scripts/package-install-smoke-consumer-git-repo-lib.ts +1 -10
- package/scripts/package-install-smoke-consumer-npm-lib.ts +9 -46
- package/scripts/pumuki-full-surface-smoke-lib.ts +37 -0
- package/scripts/pumuki-full-surface-smoke.ts +346 -0
- package/scripts/pumuki-smoke-installed-wrapper.cjs +31 -0
- package/integrations/evidence/trackingContract.ts +0 -17
- package/integrations/gate/governanceActionCatalog.ts +0 -275
- package/integrations/lifecycle/bootstrapManifest.ts +0 -248
- package/integrations/lifecycle/cliGovernanceConsole.ts +0 -69
- package/integrations/lifecycle/governanceNextAction.ts +0 -171
- package/integrations/lifecycle/governanceObservationSnapshot.ts +0 -369
- package/integrations/lifecycle/trackingState.ts +0 -403
- package/integrations/mcp/alignedPlatformGate.ts +0 -232
- package/integrations/mcp/readMcpPrePushStdin.ts +0 -7
- package/scripts/build-ruralgo-s1-evidence-pack.ts +0 -85
- package/scripts/ruralgo-s1-evidence-pack-lib.ts +0 -200
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
export const SWIFT_BLOCKED_DIALOG_SOURCE = String.raw`import AppKit
|
|
2
2
|
import Foundation
|
|
3
3
|
|
|
4
|
-
final class KeyableFloatingPanel: NSPanel {
|
|
5
|
-
override var canBecomeKey: Bool { true }
|
|
6
|
-
}
|
|
7
|
-
|
|
8
4
|
struct DialogConfig {
|
|
9
5
|
let title: String
|
|
10
6
|
let cause: String
|
|
@@ -12,7 +8,6 @@ struct DialogConfig {
|
|
|
12
8
|
let disableButton: String
|
|
13
9
|
let muteButton: String
|
|
14
10
|
let keepButton: String
|
|
15
|
-
let timeoutSeconds: Double
|
|
16
11
|
}
|
|
17
12
|
|
|
18
13
|
func parseArguments() -> DialogConfig {
|
|
@@ -23,74 +18,18 @@ func parseArguments() -> DialogConfig {
|
|
|
23
18
|
}
|
|
24
19
|
return args[index + 1]
|
|
25
20
|
}
|
|
26
|
-
let timeoutRaw = read("--timeout-seconds", fallback: "15")
|
|
27
|
-
let timeout = Double(timeoutRaw) ?? 15
|
|
28
21
|
return DialogConfig(
|
|
29
22
|
title: read("--title", fallback: "Pumuki bloqueado"),
|
|
30
23
|
cause: read("--cause", fallback: "Bloqueo detectado."),
|
|
31
24
|
remediation: read("--remediation", fallback: "Corrige el bloqueo y vuelve a ejecutar."),
|
|
32
25
|
disableButton: read("--disable-button", fallback: "Desactivar"),
|
|
33
26
|
muteButton: read("--mute-button", fallback: "Silenciar 30 min"),
|
|
34
|
-
keepButton: read("--keep-button", fallback: "Mantener activas")
|
|
35
|
-
timeoutSeconds: max(5, timeout)
|
|
27
|
+
keepButton: read("--keep-button", fallback: "Mantener activas")
|
|
36
28
|
)
|
|
37
29
|
}
|
|
38
30
|
|
|
39
|
-
final class
|
|
31
|
+
final class DialogAppDelegate: NSObject, NSApplicationDelegate {
|
|
40
32
|
private let config: DialogConfig
|
|
41
|
-
private var window: NSWindow?
|
|
42
|
-
private var chosenButton: String?
|
|
43
|
-
|
|
44
|
-
private func preferredWidth() -> CGFloat {
|
|
45
|
-
let longest = max(config.title.count, max(config.cause.count, config.remediation.count))
|
|
46
|
-
let estimated = CGFloat(longest) * 4.9 + 170
|
|
47
|
-
let visibleFrame = targetVisibleFrame()
|
|
48
|
-
let maxAllowed = max(360, visibleFrame.width - 40)
|
|
49
|
-
return min(max(360, estimated), min(620, maxAllowed))
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
private func targetVisibleFrame() -> NSRect {
|
|
53
|
-
let mouse = NSEvent.mouseLocation
|
|
54
|
-
if let screen = NSScreen.screens.first(where: { NSMouseInRect(mouse, $0.frame, false) }) {
|
|
55
|
-
return screen.visibleFrame
|
|
56
|
-
}
|
|
57
|
-
if let main = NSScreen.main {
|
|
58
|
-
return main.visibleFrame
|
|
59
|
-
}
|
|
60
|
-
if let first = NSScreen.screens.first {
|
|
61
|
-
return first.visibleFrame
|
|
62
|
-
}
|
|
63
|
-
return NSRect(x: 0, y: 0, width: 1440, height: 900)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
private func pinWindowToBottomRight() {
|
|
67
|
-
guard let panel = window else {
|
|
68
|
-
return
|
|
69
|
-
}
|
|
70
|
-
let width = panel.frame.width
|
|
71
|
-
let height = panel.frame.height
|
|
72
|
-
let margin: CGFloat = 20
|
|
73
|
-
let visibleFrame = targetVisibleFrame()
|
|
74
|
-
let target = NSRect(
|
|
75
|
-
x: visibleFrame.maxX - width - margin,
|
|
76
|
-
y: visibleFrame.minY + margin,
|
|
77
|
-
width: width,
|
|
78
|
-
height: height
|
|
79
|
-
)
|
|
80
|
-
panel.setFrame(target, display: true)
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
private func resizeWindowToContent(root: NSStackView, contentView: NSView) {
|
|
84
|
-
guard let panel = window else {
|
|
85
|
-
return
|
|
86
|
-
}
|
|
87
|
-
contentView.layoutSubtreeIfNeeded()
|
|
88
|
-
let fitting = root.fittingSize
|
|
89
|
-
let visibleFrame = targetVisibleFrame()
|
|
90
|
-
let width = min(max(panel.frame.width, fitting.width + 30), min(620, visibleFrame.width - 40))
|
|
91
|
-
let height = min(max(140, fitting.height + 30), max(180, visibleFrame.height - 40))
|
|
92
|
-
panel.setContentSize(NSSize(width: width, height: height))
|
|
93
|
-
}
|
|
94
33
|
|
|
95
34
|
init(config: DialogConfig) {
|
|
96
35
|
self.config = config
|
|
@@ -98,149 +37,30 @@ final class DialogController: NSObject, NSApplicationDelegate, NSWindowDelegate
|
|
|
98
37
|
}
|
|
99
38
|
|
|
100
39
|
func applicationDidFinishLaunching(_ notification: Notification) {
|
|
101
|
-
showWindow()
|
|
102
|
-
startTimeout()
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
private func showWindow() {
|
|
106
|
-
let width = preferredWidth()
|
|
107
|
-
let height: CGFloat = 170
|
|
108
|
-
let margin: CGFloat = 20
|
|
109
|
-
let screenFrame = targetVisibleFrame()
|
|
110
|
-
let origin = NSPoint(
|
|
111
|
-
x: screenFrame.maxX - width - margin,
|
|
112
|
-
y: screenFrame.minY + margin
|
|
113
|
-
)
|
|
114
|
-
|
|
115
|
-
let panel = KeyableFloatingPanel(
|
|
116
|
-
contentRect: NSRect(x: origin.x, y: origin.y, width: width, height: height),
|
|
117
|
-
styleMask: [.titled, .closable, .fullSizeContentView],
|
|
118
|
-
backing: .buffered,
|
|
119
|
-
defer: false
|
|
120
|
-
)
|
|
121
|
-
panel.becomesKeyOnlyIfNeeded = false
|
|
122
|
-
panel.level = .floating
|
|
123
|
-
panel.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary]
|
|
124
|
-
panel.titleVisibility = .hidden
|
|
125
|
-
panel.titlebarAppearsTransparent = true
|
|
126
|
-
panel.isMovable = true
|
|
127
|
-
panel.isReleasedWhenClosed = false
|
|
128
|
-
panel.delegate = self
|
|
129
|
-
panel.hidesOnDeactivate = false
|
|
130
|
-
panel.standardWindowButton(.zoomButton)?.isHidden = true
|
|
131
|
-
panel.standardWindowButton(.miniaturizeButton)?.isHidden = true
|
|
132
|
-
|
|
133
|
-
let root = NSStackView()
|
|
134
|
-
root.orientation = .vertical
|
|
135
|
-
root.alignment = .leading
|
|
136
|
-
root.spacing = 12
|
|
137
|
-
root.translatesAutoresizingMaskIntoConstraints = false
|
|
138
|
-
|
|
139
|
-
let titleField = NSTextField(labelWithString: config.title)
|
|
140
|
-
titleField.font = NSFont.boldSystemFont(ofSize: 15)
|
|
141
|
-
titleField.lineBreakMode = .byWordWrapping
|
|
142
|
-
titleField.maximumNumberOfLines = 0
|
|
143
|
-
titleField.preferredMaxLayoutWidth = width - 48
|
|
144
|
-
titleField.cell?.lineBreakMode = .byWordWrapping
|
|
145
|
-
titleField.cell?.usesSingleLineMode = false
|
|
146
|
-
titleField.cell?.wraps = true
|
|
147
|
-
|
|
148
|
-
let causeField = NSTextField(wrappingLabelWithString: "Causa: \(config.cause)")
|
|
149
|
-
causeField.font = NSFont.systemFont(ofSize: 12)
|
|
150
|
-
causeField.lineBreakMode = .byWordWrapping
|
|
151
|
-
causeField.maximumNumberOfLines = 0
|
|
152
|
-
causeField.preferredMaxLayoutWidth = width - 48
|
|
153
|
-
causeField.cell?.lineBreakMode = .byWordWrapping
|
|
154
|
-
causeField.cell?.usesSingleLineMode = false
|
|
155
|
-
causeField.cell?.wraps = true
|
|
156
|
-
|
|
157
|
-
let remediationField = NSTextField(wrappingLabelWithString: "Solución: \(config.remediation)")
|
|
158
|
-
remediationField.font = NSFont.systemFont(ofSize: 12)
|
|
159
|
-
remediationField.lineBreakMode = .byWordWrapping
|
|
160
|
-
remediationField.maximumNumberOfLines = 0
|
|
161
|
-
remediationField.preferredMaxLayoutWidth = width - 48
|
|
162
|
-
remediationField.cell?.lineBreakMode = .byWordWrapping
|
|
163
|
-
remediationField.cell?.usesSingleLineMode = false
|
|
164
|
-
remediationField.cell?.wraps = true
|
|
165
|
-
|
|
166
|
-
let buttons = NSStackView()
|
|
167
|
-
buttons.orientation = .horizontal
|
|
168
|
-
buttons.alignment = .centerY
|
|
169
|
-
buttons.spacing = 10
|
|
170
|
-
|
|
171
|
-
let disableButton = NSButton(title: config.disableButton, target: self, action: #selector(disablePressed))
|
|
172
|
-
disableButton.bezelStyle = .rounded
|
|
173
|
-
let muteButton = NSButton(title: config.muteButton, target: self, action: #selector(mutePressed))
|
|
174
|
-
muteButton.bezelStyle = .rounded
|
|
175
|
-
let keepButton = NSButton(title: config.keepButton, target: self, action: #selector(keepPressed))
|
|
176
|
-
keepButton.bezelStyle = .rounded
|
|
177
|
-
keepButton.keyEquivalent = "\r"
|
|
178
|
-
|
|
179
|
-
buttons.addArrangedSubview(disableButton)
|
|
180
|
-
buttons.addArrangedSubview(muteButton)
|
|
181
|
-
buttons.addArrangedSubview(keepButton)
|
|
182
|
-
|
|
183
|
-
root.addArrangedSubview(titleField)
|
|
184
|
-
root.addArrangedSubview(causeField)
|
|
185
|
-
root.addArrangedSubview(remediationField)
|
|
186
|
-
root.addArrangedSubview(buttons)
|
|
187
|
-
|
|
188
|
-
panel.contentView = NSView()
|
|
189
|
-
guard let contentView = panel.contentView else {
|
|
190
|
-
finish(with: config.keepButton)
|
|
191
|
-
return
|
|
192
|
-
}
|
|
193
|
-
contentView.addSubview(root)
|
|
194
|
-
NSLayoutConstraint.activate([
|
|
195
|
-
root.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 18),
|
|
196
|
-
root.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -18),
|
|
197
|
-
root.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 18),
|
|
198
|
-
root.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -18),
|
|
199
|
-
])
|
|
200
|
-
|
|
201
|
-
self.window = panel
|
|
202
|
-
resizeWindowToContent(root: root, contentView: contentView)
|
|
203
|
-
pinWindowToBottomRight()
|
|
204
|
-
panel.makeKeyAndOrderFront(nil)
|
|
205
|
-
panel.orderFrontRegardless()
|
|
206
40
|
NSApp.activate(ignoringOtherApps: true)
|
|
207
|
-
DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { [weak self] in
|
|
208
|
-
guard let self else {
|
|
209
|
-
return
|
|
210
|
-
}
|
|
211
|
-
self.resizeWindowToContent(root: root, contentView: contentView)
|
|
212
|
-
self.pinWindowToBottomRight()
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
41
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
42
|
+
let alert = NSAlert()
|
|
43
|
+
alert.messageText = config.title
|
|
44
|
+
alert.informativeText = "Causa: \(config.cause)\n\nSolución: \(config.remediation)"
|
|
45
|
+
alert.alertStyle = .critical
|
|
46
|
+
alert.addButton(withTitle: config.keepButton)
|
|
47
|
+
alert.addButton(withTitle: config.muteButton)
|
|
48
|
+
alert.addButton(withTitle: config.disableButton)
|
|
49
|
+
|
|
50
|
+
let response = alert.runModal()
|
|
51
|
+
let choice: String
|
|
52
|
+
switch response {
|
|
53
|
+
case .alertFirstButtonReturn:
|
|
54
|
+
choice = config.keepButton
|
|
55
|
+
case .alertSecondButtonReturn:
|
|
56
|
+
choice = config.muteButton
|
|
57
|
+
case .alertThirdButtonReturn:
|
|
58
|
+
choice = config.disableButton
|
|
59
|
+
default:
|
|
60
|
+
choice = config.keepButton
|
|
219
61
|
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
func windowWillClose(_ notification: Notification) {
|
|
223
|
-
finish(with: config.keepButton)
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
@objc private func disablePressed() {
|
|
227
|
-
finish(with: config.disableButton)
|
|
228
|
-
}
|
|
229
62
|
|
|
230
|
-
|
|
231
|
-
finish(with: config.muteButton)
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
@objc private func keepPressed() {
|
|
235
|
-
finish(with: config.keepButton)
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
private func finish(with button: String) {
|
|
239
|
-
guard chosenButton == nil else {
|
|
240
|
-
return
|
|
241
|
-
}
|
|
242
|
-
chosenButton = button
|
|
243
|
-
FileHandle.standardOutput.write(Data("button returned:\(button)\n".utf8))
|
|
63
|
+
FileHandle.standardOutput.write(Data("button returned:\(choice)\n".utf8))
|
|
244
64
|
NSApp.terminate(nil)
|
|
245
65
|
}
|
|
246
66
|
|
|
@@ -252,7 +72,7 @@ final class DialogController: NSObject, NSApplicationDelegate, NSWindowDelegate
|
|
|
252
72
|
let config = parseArguments()
|
|
253
73
|
let app = NSApplication.shared
|
|
254
74
|
app.setActivationPolicy(.accessory)
|
|
255
|
-
let
|
|
256
|
-
app.delegate =
|
|
75
|
+
let delegate = DialogAppDelegate(config: config)
|
|
76
|
+
app.delegate = delegate
|
|
257
77
|
app.run()
|
|
258
78
|
`;
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
type SystemNotificationCommandRunnerWithOutput,
|
|
6
6
|
type SystemNotificationsConfig,
|
|
7
7
|
} from './framework-menu-system-notifications-types';
|
|
8
|
+
import { isTruthyEnvValue } from './framework-menu-system-notifications-env';
|
|
8
9
|
import { resolveBlockedDialogEnabled } from './framework-menu-system-notifications-macos-dialog-enabled';
|
|
9
10
|
import { emitMacOsBannerStage } from './framework-menu-system-notifications-macos-banner-stage';
|
|
10
11
|
import { emitMacOsBlockedDialogStage } from './framework-menu-system-notifications-macos-blocked-stage';
|
|
@@ -19,6 +20,9 @@ const shouldSkipMacOsBannerForInteractiveBlockedDialog = (params: {
|
|
|
19
20
|
config: SystemNotificationsConfig;
|
|
20
21
|
env: NodeJS.ProcessEnv;
|
|
21
22
|
}): boolean => {
|
|
23
|
+
if (!isTruthyEnvValue(params.env.PUMUKI_MACOS_GATE_BLOCKED_BANNER_DEDUPE)) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
22
26
|
if (params.event.kind !== 'gate.blocked') {
|
|
23
27
|
return false;
|
|
24
28
|
}
|
|
@@ -22,7 +22,7 @@ export const buildGateBlockedPayload = (
|
|
|
22
22
|
resolveBlockedCauseSummary(event, causeCode),
|
|
23
23
|
72
|
|
24
24
|
);
|
|
25
|
-
const remediation = resolveBlockedRemediation(event, causeCode
|
|
25
|
+
const remediation = resolveBlockedRemediation(event, causeCode);
|
|
26
26
|
return {
|
|
27
27
|
title: '🔴 Pumuki bloqueado',
|
|
28
28
|
subtitle: `${projectPrefix}${event.stage} · ${causeSummary}`,
|
|
@@ -7,13 +7,7 @@ export const truncateNotificationText = (value: string, maxLength: number): stri
|
|
|
7
7
|
if (value.length <= maxLength) {
|
|
8
8
|
return value;
|
|
9
9
|
}
|
|
10
|
-
|
|
11
|
-
const trimmed = value.slice(0, limit).trimEnd();
|
|
12
|
-
const boundary = trimmed.lastIndexOf(' ');
|
|
13
|
-
const shortened = boundary >= Math.floor(limit * 0.6)
|
|
14
|
-
? trimmed.slice(0, boundary).trimEnd()
|
|
15
|
-
: trimmed;
|
|
16
|
-
return `${shortened}…`;
|
|
10
|
+
return `${value.slice(0, Math.max(1, maxLength - 1)).trimEnd()}…`;
|
|
17
11
|
};
|
|
18
12
|
|
|
19
13
|
export const resolveProjectLabel = (params: {
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
runWorkingTreeGateSilent,
|
|
17
17
|
runRepoAndStagedPrePushGateSilent,
|
|
18
18
|
runWorkingTreePrePushGateSilent,
|
|
19
|
+
runUnstagedGateSilent,
|
|
19
20
|
} from './framework-menu-gate-lib';
|
|
20
21
|
import { createFrameworkMenuPrompts } from './framework-menu-prompts';
|
|
21
22
|
import { resolveDefaultRangeFrom } from './framework-menu-runners';
|
|
@@ -24,8 +25,6 @@ import {
|
|
|
24
25
|
loadAndFormatActiveSkillsBundles,
|
|
25
26
|
} from './framework-menu-skills-lib';
|
|
26
27
|
import { isMenuUiV2Enabled } from './framework-menu-ui-version-lib';
|
|
27
|
-
import { readLifecycleStatus } from '../integrations/lifecycle/status';
|
|
28
|
-
import type { GovernanceConsoleSnapshot } from '../integrations/lifecycle/cliGovernanceConsole';
|
|
29
28
|
export * from './framework-menu-builders';
|
|
30
29
|
export { formatAdvancedMenuView } from './framework-menu-advanced-view-lib';
|
|
31
30
|
export { buildMenuGateParams } from './framework-menu-gate-lib';
|
|
@@ -44,20 +43,6 @@ const resolveInitialMenuMode = (): MenuMode => {
|
|
|
44
43
|
return 'consumer';
|
|
45
44
|
};
|
|
46
45
|
|
|
47
|
-
const readAdvancedGovernanceConsoleSnapshot = (): GovernanceConsoleSnapshot | null => {
|
|
48
|
-
try {
|
|
49
|
-
const status = readLifecycleStatus({ cwd: process.cwd() });
|
|
50
|
-
return {
|
|
51
|
-
governanceObservation: status.governanceObservation,
|
|
52
|
-
governanceNextAction: status.governanceNextAction,
|
|
53
|
-
policyValidation: status.policyValidation,
|
|
54
|
-
experimentalFeatures: status.experimentalFeatures,
|
|
55
|
-
};
|
|
56
|
-
} catch {
|
|
57
|
-
return null;
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
|
|
61
46
|
const menu = async (): Promise<void> => {
|
|
62
47
|
const rl = createInterface({ input, output });
|
|
63
48
|
|
|
@@ -78,7 +63,9 @@ const menu = async (): Promise<void> => {
|
|
|
78
63
|
runRepoGate: runRepoGateSilent,
|
|
79
64
|
runRepoAndStagedGate: runRepoAndStagedPrePushGateSilent,
|
|
80
65
|
runStagedGate: runStagedGateSilent,
|
|
66
|
+
runUnstagedGate: runUnstagedGateSilent,
|
|
81
67
|
runWorkingTreeGate: runWorkingTreePrePushGateSilent,
|
|
68
|
+
runWorkingTreePreCommitGate: runWorkingTreeGateSilent,
|
|
82
69
|
write: (text) => {
|
|
83
70
|
output.write(text);
|
|
84
71
|
},
|
|
@@ -89,14 +76,10 @@ const menu = async (): Promise<void> => {
|
|
|
89
76
|
consumerRuntime.printMenu();
|
|
90
77
|
} else {
|
|
91
78
|
const currentSummary = consumerRuntime.readCurrentSummary();
|
|
92
|
-
const lastPreflight = consumerRuntime.readLastPreflight();
|
|
93
|
-
const governanceConsole = lastPreflight ? null : readAdvancedGovernanceConsoleSnapshot();
|
|
94
79
|
if (!isMenuUiV2Enabled()) {
|
|
95
80
|
output.write(
|
|
96
81
|
`\n${formatAdvancedMenuClassicView(advancedActions, {
|
|
97
82
|
evidenceSummary: currentSummary ?? undefined,
|
|
98
|
-
preflight: lastPreflight ?? undefined,
|
|
99
|
-
governanceConsole: governanceConsole ?? undefined,
|
|
100
83
|
})}\n`
|
|
101
84
|
);
|
|
102
85
|
} else {
|
|
@@ -104,8 +87,6 @@ const menu = async (): Promise<void> => {
|
|
|
104
87
|
output.write(
|
|
105
88
|
`\n${formatAdvancedMenuView(advancedActions, {
|
|
106
89
|
evidenceSummary: currentSummary ?? undefined,
|
|
107
|
-
preflight: lastPreflight ?? undefined,
|
|
108
|
-
governanceConsole: governanceConsole ?? undefined,
|
|
109
90
|
})}\n`
|
|
110
91
|
);
|
|
111
92
|
} catch {
|
|
@@ -113,8 +94,6 @@ const menu = async (): Promise<void> => {
|
|
|
113
94
|
output.write(
|
|
114
95
|
`\n${formatAdvancedMenuClassicView(advancedActions, {
|
|
115
96
|
evidenceSummary: currentSummary ?? undefined,
|
|
116
|
-
preflight: lastPreflight ?? undefined,
|
|
117
|
-
governanceConsole: governanceConsole ?? undefined,
|
|
118
97
|
})}\n`
|
|
119
98
|
);
|
|
120
99
|
}
|
|
@@ -39,17 +39,8 @@ export const configureRemoteAndFeatureBranch = (
|
|
|
39
39
|
workspace.tmpRoot
|
|
40
40
|
);
|
|
41
41
|
runGitStep(workspace, ['remote', 'add', 'origin', workspace.bareRemote], 'git remote add origin');
|
|
42
|
+
runGitStep(workspace, ['push', '-u', 'origin', 'main'], 'git push origin main');
|
|
42
43
|
runGitStep(workspace, ['checkout', '-b', 'feature/package-smoke'], 'git checkout feature branch');
|
|
43
|
-
runGitStep(
|
|
44
|
-
workspace,
|
|
45
|
-
['push', '-u', 'origin', 'feature/package-smoke'],
|
|
46
|
-
'git push origin feature branch'
|
|
47
|
-
);
|
|
48
|
-
runGitStep(
|
|
49
|
-
workspace,
|
|
50
|
-
['push', 'origin', 'HEAD:refs/heads/main'],
|
|
51
|
-
'git push origin main from feature branch'
|
|
52
|
-
);
|
|
53
44
|
runGitStep(
|
|
54
45
|
workspace,
|
|
55
46
|
['branch', '--set-upstream-to=origin/main', 'feature/package-smoke'],
|
|
@@ -15,11 +15,10 @@ import packageJson from '../package.json';
|
|
|
15
15
|
const runNpmStep = (
|
|
16
16
|
workspace: SmokeWorkspace,
|
|
17
17
|
args: string[],
|
|
18
|
-
context: string
|
|
19
|
-
env?: NodeJS.ProcessEnv
|
|
18
|
+
context: string
|
|
20
19
|
): void => {
|
|
21
20
|
assertSuccess(
|
|
22
|
-
runCommand({ cwd: workspace.consumerRepo, executable: 'npm', args
|
|
21
|
+
runCommand({ cwd: workspace.consumerRepo, executable: 'npm', args }),
|
|
23
22
|
context
|
|
24
23
|
);
|
|
25
24
|
};
|
|
@@ -33,14 +32,7 @@ export const installTarballIntoConsumerRepo = (
|
|
|
33
32
|
'node_modules/\n.ai_evidence.json\n',
|
|
34
33
|
'utf8'
|
|
35
34
|
);
|
|
36
|
-
runNpmStep(
|
|
37
|
-
workspace,
|
|
38
|
-
['install', workspace.tarballPath ?? ''],
|
|
39
|
-
'npm install <tarball>',
|
|
40
|
-
{
|
|
41
|
-
PUMUKI_SKIP_POSTINSTALL: '1',
|
|
42
|
-
}
|
|
43
|
-
);
|
|
35
|
+
runNpmStep(workspace, ['install', workspace.tarballPath ?? ''], 'npm install <tarball>');
|
|
44
36
|
};
|
|
45
37
|
|
|
46
38
|
export const verifyInstalledPackageCanBeRequired = (
|
|
@@ -59,40 +51,10 @@ export const verifyInstalledPackageCanBeRequired = (
|
|
|
59
51
|
export const verifyInstalledPumukiBinaryVersion = (
|
|
60
52
|
workspace: SmokeWorkspace
|
|
61
53
|
): void => {
|
|
62
|
-
const hasInstalledStatusVersion = (
|
|
63
|
-
result: ReturnType<typeof runCommand>,
|
|
64
|
-
): boolean => {
|
|
65
|
-
let parsed: unknown;
|
|
66
|
-
try {
|
|
67
|
-
parsed = JSON.parse(result.stdout);
|
|
68
|
-
} catch {
|
|
69
|
-
return false;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const packageVersion =
|
|
73
|
-
typeof parsed === 'object' && parsed !== null && 'packageVersion' in parsed
|
|
74
|
-
? (parsed as { packageVersion?: unknown }).packageVersion
|
|
75
|
-
: null;
|
|
76
|
-
const effectiveVersion =
|
|
77
|
-
typeof parsed === 'object'
|
|
78
|
-
&& parsed !== null
|
|
79
|
-
&& 'version' in parsed
|
|
80
|
-
&& typeof (parsed as { version?: unknown }).version === 'object'
|
|
81
|
-
&& (parsed as { version: { effective?: unknown } }).version !== null
|
|
82
|
-
? (parsed as { version: { effective?: unknown } }).version.effective
|
|
83
|
-
: null;
|
|
84
|
-
|
|
85
|
-
return packageVersion === packageJson.version || effectiveVersion === packageJson.version;
|
|
86
|
-
};
|
|
87
|
-
|
|
88
54
|
const assertInstalledStatusVersion = (
|
|
89
55
|
result: ReturnType<typeof runCommand>,
|
|
90
56
|
context: string
|
|
91
57
|
): void => {
|
|
92
|
-
if (hasInstalledStatusVersion(result)) {
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
58
|
let parsed: unknown;
|
|
97
59
|
try {
|
|
98
60
|
parsed = JSON.parse(result.stdout);
|
|
@@ -113,9 +75,11 @@ export const verifyInstalledPumukiBinaryVersion = (
|
|
|
113
75
|
? (parsed as { version: { effective?: unknown } }).version.effective
|
|
114
76
|
: null;
|
|
115
77
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
78
|
+
if (packageVersion !== packageJson.version && effectiveVersion !== packageJson.version) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
`${context} reported unexpected version (packageVersion=${String(packageVersion)}, effectiveVersion=${String(effectiveVersion)}, expected=${packageJson.version})`
|
|
81
|
+
);
|
|
82
|
+
}
|
|
119
83
|
};
|
|
120
84
|
|
|
121
85
|
const noInstallVersionCheck = runCommand({
|
|
@@ -129,8 +93,7 @@ export const verifyInstalledPumukiBinaryVersion = (
|
|
|
129
93
|
noInstallVersionCheck.exitCode === 0
|
|
130
94
|
&& !/Cannot find module|ERR_MODULE_NOT_FOUND|failed to resolve tsx runtime/.test(
|
|
131
95
|
noInstallVersionCheck.combined
|
|
132
|
-
)
|
|
133
|
-
&& hasInstalledStatusVersion(noInstallVersionCheck);
|
|
96
|
+
);
|
|
134
97
|
if (noInstallPassed) {
|
|
135
98
|
assertNoFatalOutput(noInstallVersionCheck, 'pumuki status --json smoke');
|
|
136
99
|
assertInstalledStatusVersion(noInstallVersionCheck, 'pumuki status --json smoke');
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { dirname, join } from 'node:path';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
|
|
4
|
+
export type SmokeBinStrategy = 'source' | 'installed';
|
|
5
|
+
|
|
6
|
+
export const resolveBinStrategy = (raw: string | undefined): SmokeBinStrategy => {
|
|
7
|
+
const normalized = (raw ?? 'source').trim().toLowerCase();
|
|
8
|
+
if (normalized === 'installed' || normalized === 'consumer') {
|
|
9
|
+
return 'installed';
|
|
10
|
+
}
|
|
11
|
+
return 'source';
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type SmokeLayout = {
|
|
15
|
+
pumukiPackageRoot: string;
|
|
16
|
+
smokeCwd: string;
|
|
17
|
+
binStrategy: SmokeBinStrategy;
|
|
18
|
+
binRoot: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const resolveSmokeLayout = (params: {
|
|
22
|
+
scriptFileUrl: string;
|
|
23
|
+
env: NodeJS.ProcessEnv;
|
|
24
|
+
}): SmokeLayout => {
|
|
25
|
+
const pumukiPackageRoot = join(dirname(fileURLToPath(params.scriptFileUrl)), '..');
|
|
26
|
+
const smokeCwd =
|
|
27
|
+
(params.env.PUMUKI_SMOKE_REPO_ROOT ?? pumukiPackageRoot).trim() || pumukiPackageRoot;
|
|
28
|
+
const binStrategy = resolveBinStrategy(params.env.PUMUKI_SMOKE_BIN_STRATEGY);
|
|
29
|
+
const binRoot =
|
|
30
|
+
binStrategy === 'installed'
|
|
31
|
+
? join(smokeCwd, 'node_modules', 'pumuki')
|
|
32
|
+
: pumukiPackageRoot;
|
|
33
|
+
return { pumukiPackageRoot, smokeCwd, binStrategy, binRoot };
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const installedBinMarkerPath = (layout: SmokeLayout): string =>
|
|
37
|
+
join(layout.binRoot, 'bin', 'pumuki.js');
|