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.
Files changed (99) hide show
  1. package/CHANGELOG.md +52 -5
  2. package/README.md +4 -2
  3. package/VERSION +1 -1
  4. package/core/facts/detectors/typescript/index.test.ts +0 -229
  5. package/core/facts/detectors/typescript/index.ts +0 -278
  6. package/core/facts/extractHeuristicFacts.ts +0 -4
  7. package/core/rules/presets/heuristics/typescript.test.ts +1 -21
  8. package/core/rules/presets/heuristics/typescript.ts +0 -72
  9. package/docs/README.md +13 -9
  10. package/docs/codex-skills/backend-enterprise-rules.md +3 -3
  11. package/docs/operations/RELEASE_NOTES.md +41 -4
  12. package/docs/product/API_REFERENCE.md +1 -1
  13. package/docs/product/HOW_IT_WORKS.md +6 -0
  14. package/docs/product/INSTALLATION.md +1 -1
  15. package/docs/product/USAGE.md +42 -5
  16. package/docs/tracking/plan-curso-pumuki-stack-my-architecture.md +100 -44
  17. package/docs/validation/README.md +6 -3
  18. package/integrations/config/skillsDetectorRegistry.ts +0 -24
  19. package/integrations/config/skillsMarkdownRules.ts +0 -57
  20. package/integrations/evidence/buildEvidence.ts +0 -24
  21. package/integrations/evidence/repoState.ts +9 -7
  22. package/integrations/evidence/schema.ts +0 -18
  23. package/integrations/evidence/writeEvidence.ts +0 -24
  24. package/integrations/gate/evaluateAiGate.ts +8 -251
  25. package/integrations/gate/remediationCatalog.ts +0 -8
  26. package/integrations/git/GitService.ts +44 -5
  27. package/integrations/git/aiGateRepoPolicyFindings.ts +86 -17
  28. package/integrations/git/runPlatformGate.ts +1 -9
  29. package/integrations/git/runPlatformGateFacts.ts +19 -1
  30. package/integrations/git/runPlatformGateOutput.ts +41 -42
  31. package/integrations/lifecycle/adapter.templates.json +1 -0
  32. package/integrations/lifecycle/adapter.ts +0 -24
  33. package/integrations/lifecycle/audit.ts +101 -0
  34. package/integrations/lifecycle/cli.ts +120 -99
  35. package/integrations/lifecycle/cliSdd.ts +4 -26
  36. package/integrations/lifecycle/doctor.ts +40 -102
  37. package/integrations/lifecycle/index.ts +2 -0
  38. package/integrations/lifecycle/install.ts +0 -21
  39. package/integrations/lifecycle/packageInfo.ts +1 -118
  40. package/integrations/lifecycle/state.ts +1 -8
  41. package/integrations/lifecycle/status.ts +40 -59
  42. package/integrations/lifecycle/watch.ts +1 -1
  43. package/integrations/mcp/aiGateCheck.ts +10 -194
  44. package/integrations/mcp/autoExecuteAiStart.ts +116 -92
  45. package/integrations/mcp/enterpriseServer.ts +7 -23
  46. package/integrations/mcp/enterpriseStdioServer.cli.ts +4 -31
  47. package/integrations/mcp/preFlightCheck.ts +5 -67
  48. package/integrations/platform/detectPlatforms.ts +37 -0
  49. package/integrations/sdd/policy.ts +28 -20
  50. package/package.json +1 -1
  51. package/scripts/check-tracking-single-active.sh +1 -1
  52. package/scripts/consumer-menu-matrix-baseline-report-lib.ts +13 -38
  53. package/scripts/consumer-postinstall-resolve-args.cjs +44 -0
  54. package/scripts/consumer-postinstall.cjs +76 -21
  55. package/scripts/framework-menu-advanced-view-lib.ts +0 -49
  56. package/scripts/framework-menu-consumer-actions-lib.ts +28 -4
  57. package/scripts/framework-menu-consumer-preflight-hints.ts +5 -2
  58. package/scripts/framework-menu-consumer-preflight-render.ts +0 -10
  59. package/scripts/framework-menu-consumer-preflight-run.ts +0 -23
  60. package/scripts/framework-menu-consumer-preflight-types.ts +0 -12
  61. package/scripts/framework-menu-consumer-runtime-actions.ts +87 -17
  62. package/scripts/framework-menu-consumer-runtime-audit.ts +36 -2
  63. package/scripts/framework-menu-consumer-runtime-evidence-classic.ts +140 -0
  64. package/scripts/framework-menu-consumer-runtime-lib.ts +2 -38
  65. package/scripts/framework-menu-consumer-runtime-menu.ts +4 -31
  66. package/scripts/framework-menu-consumer-runtime-types.ts +3 -5
  67. package/scripts/framework-menu-evidence-summary-lib.ts +1 -0
  68. package/scripts/framework-menu-evidence-summary-read.ts +57 -5
  69. package/scripts/framework-menu-evidence-summary-severity.ts +3 -1
  70. package/scripts/framework-menu-evidence-summary-types.ts +7 -0
  71. package/scripts/framework-menu-gate-lib.ts +9 -0
  72. package/scripts/framework-menu-layout-data.ts +5 -0
  73. package/scripts/framework-menu-matrix-baseline-lib.ts +15 -14
  74. package/scripts/framework-menu-matrix-canary-lib.ts +22 -1
  75. package/scripts/framework-menu-matrix-evidence-lib.ts +1 -0
  76. package/scripts/framework-menu-matrix-evidence-types.ts +13 -1
  77. package/scripts/framework-menu-matrix-runner-lib.ts +35 -0
  78. package/scripts/framework-menu-system-notifications-cause.ts +0 -3
  79. package/scripts/framework-menu-system-notifications-macos-swift-source.ts +24 -204
  80. package/scripts/framework-menu-system-notifications-macos.ts +4 -0
  81. package/scripts/framework-menu-system-notifications-payloads-blocked.ts +1 -1
  82. package/scripts/framework-menu-system-notifications-text.ts +1 -7
  83. package/scripts/framework-menu.ts +3 -24
  84. package/scripts/package-install-smoke-consumer-git-repo-lib.ts +1 -10
  85. package/scripts/package-install-smoke-consumer-npm-lib.ts +9 -46
  86. package/scripts/pumuki-full-surface-smoke-lib.ts +37 -0
  87. package/scripts/pumuki-full-surface-smoke.ts +346 -0
  88. package/scripts/pumuki-smoke-installed-wrapper.cjs +31 -0
  89. package/integrations/evidence/trackingContract.ts +0 -17
  90. package/integrations/gate/governanceActionCatalog.ts +0 -275
  91. package/integrations/lifecycle/bootstrapManifest.ts +0 -248
  92. package/integrations/lifecycle/cliGovernanceConsole.ts +0 -69
  93. package/integrations/lifecycle/governanceNextAction.ts +0 -171
  94. package/integrations/lifecycle/governanceObservationSnapshot.ts +0 -369
  95. package/integrations/lifecycle/trackingState.ts +0 -403
  96. package/integrations/mcp/alignedPlatformGate.ts +0 -232
  97. package/integrations/mcp/readMcpPrePushStdin.ts +0 -7
  98. package/scripts/build-ruralgo-s1-evidence-pack.ts +0 -85
  99. 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 DialogController: NSObject, NSApplicationDelegate, NSWindowDelegate {
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
- private func startTimeout() {
217
- Timer.scheduledTimer(withTimeInterval: config.timeoutSeconds, repeats: false) { [weak self] _ in
218
- self?.finish(with: self?.config.keepButton ?? "Mantener activas")
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
- @objc private func mutePressed() {
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 controller = DialogController(config: config)
256
- app.delegate = controller
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, { variant: 'banner' });
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
- const limit = Math.max(1, maxLength - 1);
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, env }),
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
- throw new Error(
117
- `${context} reported unexpected version (packageVersion=${String(packageVersion)}, effectiveVersion=${String(effectiveVersion)}, expected=${packageJson.version})`
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');