pumuki 6.3.71 → 6.3.73

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 (36) hide show
  1. package/AGENTS.md +269 -0
  2. package/CHANGELOG.md +686 -0
  3. package/README.md +32 -0
  4. package/VERSION +1 -1
  5. package/docs/README.md +7 -2
  6. package/docs/operations/RELEASE_NOTES.md +18 -0
  7. package/docs/product/USAGE.md +10 -0
  8. package/docs/tracking/plan-curso-pumuki-stack-my-architecture.md +62 -0
  9. package/integrations/gate/governanceActionCatalog.ts +230 -0
  10. package/integrations/git/GitService.ts +25 -0
  11. package/integrations/git/runPlatformGate.ts +9 -1
  12. package/integrations/git/runPlatformGateFacts.ts +1 -0
  13. package/integrations/git/runPlatformGateOutput.ts +36 -27
  14. package/integrations/lifecycle/adapter.templates.json +3 -0
  15. package/integrations/lifecycle/audit.ts +101 -0
  16. package/integrations/lifecycle/cli.ts +80 -8
  17. package/integrations/lifecycle/doctor.ts +64 -1
  18. package/integrations/lifecycle/governanceNextAction.ts +164 -0
  19. package/integrations/lifecycle/governanceObservationSnapshot.ts +288 -0
  20. package/integrations/lifecycle/index.ts +2 -0
  21. package/integrations/lifecycle/status.ts +29 -2
  22. package/integrations/mcp/autoExecuteAiStart.ts +86 -84
  23. package/integrations/mcp/preFlightCheck.ts +40 -3
  24. package/integrations/platform/detectPlatforms.ts +37 -0
  25. package/integrations/sdd/openSpecCli.ts +12 -3
  26. package/package.json +11 -1
  27. package/scripts/build-ruralgo-s1-evidence-pack.ts +85 -0
  28. package/scripts/consumer-postinstall-resolve-args.cjs +38 -0
  29. package/scripts/consumer-postinstall.cjs +10 -1
  30. package/scripts/framework-menu-consumer-preflight-render.ts +6 -0
  31. package/scripts/framework-menu-consumer-preflight-run.ts +19 -0
  32. package/scripts/framework-menu-consumer-preflight-types.ts +8 -0
  33. package/scripts/pumuki-full-surface-smoke-lib.ts +37 -0
  34. package/scripts/pumuki-full-surface-smoke.ts +261 -0
  35. package/scripts/pumuki-smoke-installed-wrapper.cjs +31 -0
  36. package/scripts/ruralgo-s1-evidence-pack-lib.ts +200 -0
@@ -17,20 +17,29 @@ type OpenSpecCommandResult = {
17
17
  stderr: string;
18
18
  };
19
19
 
20
- const resolveOpenSpecBinary = (repoRoot: string): string => {
20
+ const resolveOpenSpecBinary = (repoRoot: string): string | undefined => {
21
21
  const binaryName = process.platform === 'win32' ? 'openspec.cmd' : 'openspec';
22
22
  const localBinaryPath = join(repoRoot, 'node_modules', '.bin', binaryName);
23
23
  if (existsSync(localBinaryPath)) {
24
24
  return localBinaryPath;
25
25
  }
26
- return 'openspec';
26
+ return undefined;
27
27
  };
28
28
 
29
29
  const runOpenSpecCommand = (
30
30
  args: ReadonlyArray<string>,
31
31
  cwd: string
32
32
  ): OpenSpecCommandResult => {
33
- const result = runSpawnSync(resolveOpenSpecBinary(cwd), [...args], {
33
+ const binary = resolveOpenSpecBinary(cwd);
34
+ if (!binary) {
35
+ return {
36
+ exitCode: 127,
37
+ stdout: '',
38
+ stderr:
39
+ 'OpenSpec CLI not found under repo node_modules/.bin (add @fission-ai/openspec to this repository).',
40
+ };
41
+ }
42
+ const result = runSpawnSync(binary, [...args], {
34
43
  cwd,
35
44
  encoding: 'utf8',
36
45
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pumuki",
3
- "version": "6.3.71",
3
+ "version": "6.3.73",
4
4
  "description": "Enterprise-grade AST Intelligence System with multi-platform support (iOS, Android, Backend, Frontend) and Feature-First + DDD + Clean Architecture enforcement. Includes dynamic violations API for intelligent querying.",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -54,6 +54,8 @@
54
54
  "test:saas-ingestion": "npx --yes tsx@4.21.0 --test integrations/lifecycle/__tests__/saasIngestionContract.test.ts integrations/lifecycle/__tests__/saasIngestionBuilder.test.ts integrations/lifecycle/__tests__/saasIngestionTransport.test.ts integrations/lifecycle/__tests__/saasIngestionIdempotency.test.ts integrations/lifecycle/__tests__/saasIngestionAuth.test.ts integrations/lifecycle/__tests__/saasIngestionAudit.test.ts integrations/lifecycle/__tests__/saasIngestionMetrics.test.ts integrations/lifecycle/__tests__/saasIngestionGovernance.test.ts integrations/lifecycle/__tests__/saasFederation.test.ts integrations/lifecycle/__tests__/saasEnterpriseAnalytics.test.ts integrations/lifecycle/__tests__/cli.test.ts",
55
55
  "test:operational-memory": "npx --yes tsx@4.21.0 --test integrations/lifecycle/__tests__/operationalMemoryContract.test.ts integrations/lifecycle/__tests__/operationalMemorySignals.test.ts integrations/lifecycle/__tests__/operationalMemorySnapshot.test.ts integrations/git/__tests__/runPlatformGate.test.ts integrations/git/__tests__/runPlatformGateEvidence.test.ts integrations/evidence/__tests__/buildEvidence.test.ts integrations/evidence/writeEvidence.test.ts integrations/evidence/generateEvidence.test.ts",
56
56
  "test:stage-gates": "npx --yes tsx@4.21.0 --test integrations/config/__tests__/*.test.ts integrations/gate/__tests__/*.test.ts integrations/git/__tests__/*.test.ts integrations/lifecycle/__tests__/*.test.ts integrations/sdd/__tests__/*.test.ts scripts/__tests__/*.test.ts",
57
+ "smoke:pumuki-surface": "npx --yes tsx@4.21.0 scripts/pumuki-full-surface-smoke.ts",
58
+ "smoke:pumuki-surface-installed": "node scripts/pumuki-smoke-installed-wrapper.cjs",
57
59
  "test:deterministic": "npm run test:evidence && npm run test:mcp && npm run test:heuristics",
58
60
  "ast:refresh": "node bin/pumuki-pre-commit.js",
59
61
  "ast:audit": "node bin/pumuki-pre-commit.js",
@@ -144,11 +146,14 @@
144
146
  "validation:phase5-escalation:payload": "bash scripts/build-phase5-support-portal-payload.sh",
145
147
  "validation:architecture-guardrails": "npx --yes tsx@4.21.0 --test scripts/__tests__/architecture-file-size-guardrails.test.ts",
146
148
  "validation:package-manifest": "node --import tsx scripts/check-package-manifest.ts",
149
+ "validation:pumuki-surface-smoke": "npm run -s smoke:pumuki-surface",
150
+ "validation:local-merge-bar": "npm run -s typecheck && npm run -s validation:pumuki-surface-smoke && npm test",
147
151
  "validation:package-smoke": "node --import tsx scripts/package-install-smoke.ts --mode=block",
148
152
  "validation:package-smoke:minimal": "node --import tsx scripts/package-install-smoke.ts --mode=minimal",
149
153
  "validation:lifecycle-smoke": "node --import tsx scripts/package-install-smoke.ts --mode=minimal",
150
154
  "validation:contract-suite:enterprise": "node --import tsx scripts/run-enterprise-contract-suite.ts",
151
155
  "validation:consumer-matrix-baseline": "node --import tsx scripts/build-consumer-menu-matrix-baseline.ts",
156
+ "validation:ruralgo-s1-evidence-pack": "npx --yes tsx@4.21.0 scripts/build-ruralgo-s1-evidence-pack.ts",
152
157
  "validation:c020-benchmark": "node --import tsx scripts/run-c020-benchmark.ts",
153
158
  "validation:clean-artifacts": "npx --yes tsx@4.21.0 scripts/clean-validation-artifacts.ts",
154
159
  "skills:compile": "npx --yes tsx@4.21.0 scripts/compile-skills-lock.ts",
@@ -248,6 +253,8 @@
248
253
  "integrations/telemetry/*.ts",
249
254
  "scripts/*.ts",
250
255
  "scripts/consumer-postinstall.cjs",
256
+ "scripts/consumer-postinstall-resolve-args.cjs",
257
+ "scripts/pumuki-smoke-installed-wrapper.cjs",
251
258
  "scripts/adapters/*.ts",
252
259
  "scripts/adapters/*.md",
253
260
  "scripts/*.sh",
@@ -259,10 +266,13 @@
259
266
  "docs/product/*.md",
260
267
  "docs/rule-packs/*.md",
261
268
  "docs/validation/*.md",
269
+ "docs/tracking/plan-curso-pumuki-stack-my-architecture.md",
262
270
  "assets/**/*",
263
271
  "vendor/skills/**/*",
264
272
  "index.js",
265
273
  "README.md",
274
+ "AGENTS.md",
275
+ "CHANGELOG.md",
266
276
  "LICENSE",
267
277
  "VERSION",
268
278
  "tsconfig.json",
@@ -0,0 +1,85 @@
1
+ import {
2
+ buildRuralGoS1EvidencePackMarkdown,
3
+ writeRuralGoS1EvidencePack,
4
+ } from './ruralgo-s1-evidence-pack-lib';
5
+
6
+ type CliOptions = {
7
+ consumerRoot: string;
8
+ outFile: string;
9
+ packageVersion: string;
10
+ generatedAt: string;
11
+ };
12
+
13
+ const parseArgs = (argv: ReadonlyArray<string>): CliOptions => {
14
+ const options: CliOptions = {
15
+ consumerRoot: '<RURALGO_REPO_ROOT>',
16
+ outFile: '.audit-reports/ruralgo-s1/ruralgo-s1-evidence-pack.md',
17
+ packageVersion: 'unknown',
18
+ generatedAt: new Date().toISOString(),
19
+ };
20
+
21
+ for (let index = 0; index < argv.length; index += 1) {
22
+ const token = argv[index];
23
+ const next = argv[index + 1];
24
+ switch (token) {
25
+ case '--consumer-root':
26
+ if (!next) {
27
+ throw new Error('missing value for --consumer-root');
28
+ }
29
+ options.consumerRoot = next;
30
+ index += 1;
31
+ break;
32
+ case '--out':
33
+ if (!next) {
34
+ throw new Error('missing value for --out');
35
+ }
36
+ options.outFile = next;
37
+ index += 1;
38
+ break;
39
+ case '--package-version':
40
+ if (!next) {
41
+ throw new Error('missing value for --package-version');
42
+ }
43
+ options.packageVersion = next;
44
+ index += 1;
45
+ break;
46
+ case '--generated-at':
47
+ if (!next) {
48
+ throw new Error('missing value for --generated-at');
49
+ }
50
+ options.generatedAt = next;
51
+ index += 1;
52
+ break;
53
+ default:
54
+ throw new Error(`unknown argument: ${token}`);
55
+ }
56
+ }
57
+
58
+ return options;
59
+ };
60
+
61
+ const main = (): number => {
62
+ const cwd = process.cwd();
63
+ const options = parseArgs(process.argv.slice(2));
64
+ const markdown = buildRuralGoS1EvidencePackMarkdown({
65
+ cwd,
66
+ consumerRoot: options.consumerRoot,
67
+ packageVersion: options.packageVersion,
68
+ generatedAt: options.generatedAt,
69
+ });
70
+ const outputPath = writeRuralGoS1EvidencePack({
71
+ cwd,
72
+ outFile: options.outFile,
73
+ markdown,
74
+ });
75
+ process.stdout.write(`ruralgo s1 evidence pack generated at ${outputPath}\n`);
76
+ return 0;
77
+ };
78
+
79
+ try {
80
+ process.exitCode = main();
81
+ } catch (error) {
82
+ const message = error instanceof Error ? error.message : 'unknown error';
83
+ process.stderr.write(`ruralgo s1 evidence pack failed: ${message}\n`);
84
+ process.exitCode = 1;
85
+ }
@@ -0,0 +1,38 @@
1
+ 'use strict';
2
+
3
+ const KNOWN_MCP_AGENTS = new Set(['cursor', 'codex', 'claude', 'repo']);
4
+
5
+ const normalizeExplicitAgent = (raw) => {
6
+ const trimmed = (raw ?? '').trim();
7
+ if (!trimmed) {
8
+ return '';
9
+ }
10
+ const lower = trimmed.toLowerCase();
11
+ if (lower === '0' || lower === 'none' || lower === 'false') {
12
+ return '';
13
+ }
14
+ if (!KNOWN_MCP_AGENTS.has(lower)) {
15
+ return '';
16
+ }
17
+ return lower;
18
+ };
19
+
20
+ const resolveConsumerPostinstallInstallExtras = (_consumerRoot, env = process.env) => {
21
+ if (env.PUMUKI_POSTINSTALL_SKIP_MCP === '1') {
22
+ return { extras: [], reason: 'skip_mcp' };
23
+ }
24
+ const explicit = normalizeExplicitAgent(env.PUMUKI_POSTINSTALL_MCP_AGENT);
25
+ if (explicit) {
26
+ return { extras: ['--with-mcp', `--agent=${explicit}`], reason: 'explicit_agent' };
27
+ }
28
+ return {
29
+ extras: ['--with-mcp', '--agent=repo'],
30
+ reason: 'default_repo_adapter',
31
+ };
32
+ };
33
+
34
+ module.exports = {
35
+ KNOWN_MCP_AGENTS,
36
+ resolveConsumerPostinstallInstallExtras,
37
+ normalizeExplicitAgent,
38
+ };
@@ -4,6 +4,7 @@
4
4
  const { existsSync } = require('node:fs');
5
5
  const { join, resolve } = require('node:path');
6
6
  const { spawnSync } = require('node:child_process');
7
+ const { resolveConsumerPostinstallInstallExtras } = require('./consumer-postinstall-resolve-args.cjs');
7
8
 
8
9
  const skipReason = () => {
9
10
  if (process.env.PUMUKI_SKIP_POSTINSTALL === '1') {
@@ -41,7 +42,15 @@ const main = () => {
41
42
  PUMUKI_SKIP_OPENSPEC_BOOTSTRAP: process.env.PUMUKI_SKIP_OPENSPEC_BOOTSTRAP ?? '1',
42
43
  };
43
44
 
44
- const result = spawnSync(process.execPath, [pumukiCli, 'install'], {
45
+ const { extras: installExtras, reason: mcpReason } = resolveConsumerPostinstallInstallExtras(
46
+ consumerRoot,
47
+ env
48
+ );
49
+ if (installExtras.length > 0 && env.PUMUKI_VERBOSE_INSTALL === '1') {
50
+ console.debug(`[pumuki] postinstall: mcp wiring (${mcpReason}): ${installExtras.join(' ')}`);
51
+ }
52
+
53
+ const result = spawnSync(process.execPath, [pumukiCli, 'install', ...installExtras], {
45
54
  cwd: consumerRoot,
46
55
  env,
47
56
  stdio: 'inherit',
@@ -1,5 +1,7 @@
1
1
  import { renderLegacyPanel, resolveLegacyPanelOuterWidth } from './framework-menu-legacy-audit-lib';
2
2
  import { buildConsumerPreflightBlockingCauseLines } from './framework-menu-consumer-preflight-hints';
3
+ import { buildGovernanceNextActionSummaryLines } from '../integrations/lifecycle/governanceNextAction';
4
+ import { buildGovernanceObservationSummaryLines } from '../integrations/lifecycle/governanceObservationSnapshot';
3
5
  import type {
4
6
  ConsumerPreflightRenderOptions,
5
7
  ConsumerPreflightResult,
@@ -19,6 +21,10 @@ const buildConsumerPreflightPanelLines = (
19
21
  `Evidence source: source=${evidence.source.source} path=${evidence.source.path} digest=${evidence.source.digest ?? 'null'} generated_at=${evidence.source.generated_at ?? 'null'}`,
20
22
  `Gate: ${preflight.status} (${preflight.result.violations.length} violations)`,
21
23
  ];
24
+ lines.push('', 'Governance truth:');
25
+ lines.push(...buildGovernanceObservationSummaryLines(preflight.governanceObservation));
26
+ lines.push('', 'Governance next action:');
27
+ lines.push(...buildGovernanceNextActionSummaryLines(preflight.governanceNextAction));
22
28
  lines.push(...buildConsumerPreflightBlockingCauseLines(preflight));
23
29
 
24
30
  if (preflight.hints.length > 0) {
@@ -2,6 +2,11 @@ import {
2
2
  evaluateAiGate,
3
3
  type AiGateCheckResult,
4
4
  } from '../integrations/gate/evaluateAiGate';
5
+ import { readLifecycleExperimentalFeaturesSnapshot } from '../integrations/lifecycle/experimentalFeaturesSnapshot';
6
+ import { LifecycleGitService } from '../integrations/lifecycle/gitService';
7
+ import { readGovernanceObservationSnapshot } from '../integrations/lifecycle/governanceObservationSnapshot';
8
+ import { readGovernanceNextAction } from '../integrations/lifecycle/governanceNextAction';
9
+ import { readLifecyclePolicyValidationSnapshot } from '../integrations/lifecycle/policyValidationSnapshot';
5
10
  import {
6
11
  emitSystemNotification,
7
12
  type PumukiCriticalNotificationEvent,
@@ -28,6 +33,7 @@ const defaultDependencies: ConsumerPreflightDependencies = {
28
33
  event: params.event,
29
34
  repoRoot: params.repoRoot,
30
35
  }),
36
+ readGovernanceNextAction,
31
37
  };
32
38
 
33
39
  const buildNotificationEvents = (
@@ -86,6 +92,17 @@ export const runConsumerPreflight = (
86
92
  repoRoot,
87
93
  stage: params.stage,
88
94
  });
95
+ const governanceObservation = readGovernanceObservationSnapshot({
96
+ repoRoot,
97
+ experimentalFeatures: readLifecycleExperimentalFeaturesSnapshot(),
98
+ policyValidation: readLifecyclePolicyValidationSnapshot(repoRoot),
99
+ git: new LifecycleGitService(),
100
+ });
101
+ const governanceNextAction = activeDependencies.readGovernanceNextAction({
102
+ repoRoot,
103
+ stage: params.stage,
104
+ governanceObservation,
105
+ });
89
106
  const hints = buildConsumerPreflightHints(result, params.stage);
90
107
  const notificationEvents = buildNotificationEvents(result);
91
108
  const notificationResults = notificationEvents.map((event) =>
@@ -99,6 +116,8 @@ export const runConsumerPreflight = (
99
116
  stage: params.stage,
100
117
  status: result.status,
101
118
  result,
119
+ governanceObservation,
120
+ governanceNextAction,
102
121
  hints,
103
122
  notificationResults,
104
123
  };
@@ -2,6 +2,11 @@ import type {
2
2
  AiGateCheckResult,
3
3
  AiGateViolation,
4
4
  } from '../integrations/gate/evaluateAiGate';
5
+ import type {
6
+ GovernanceNextActionReader,
7
+ GovernanceNextActionSummary,
8
+ } from '../integrations/lifecycle/governanceNextAction';
9
+ import type { GovernanceObservationSnapshot } from '../integrations/lifecycle/governanceObservationSnapshot';
5
10
  import type {
6
11
  PumukiCriticalNotificationEvent,
7
12
  SystemNotificationEmitResult,
@@ -13,6 +18,8 @@ export type ConsumerPreflightResult = {
13
18
  stage: ConsumerPreflightStage;
14
19
  status: AiGateCheckResult['status'];
15
20
  result: AiGateCheckResult;
21
+ governanceObservation: GovernanceObservationSnapshot;
22
+ governanceNextAction: GovernanceNextActionSummary;
16
23
  hints: ReadonlyArray<string>;
17
24
  notificationResults: ReadonlyArray<SystemNotificationEmitResult>;
18
25
  };
@@ -26,6 +33,7 @@ export type ConsumerPreflightDependencies = {
26
33
  event: PumukiCriticalNotificationEvent;
27
34
  repoRoot: string;
28
35
  }) => SystemNotificationEmitResult;
36
+ readGovernanceNextAction: GovernanceNextActionReader;
29
37
  };
30
38
 
31
39
  export type ConsumerPreflightRenderOptions = {
@@ -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');
@@ -0,0 +1,261 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { existsSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import {
5
+ installedBinMarkerPath,
6
+ resolveSmokeLayout,
7
+ } from './pumuki-full-surface-smoke-lib';
8
+
9
+ type SmokeRow = {
10
+ id: string;
11
+ bin: string;
12
+ args: ReadonlyArray<string>;
13
+ timeoutMs: number;
14
+ allowNonZero?: boolean;
15
+ };
16
+
17
+ const layout = resolveSmokeLayout({
18
+ scriptFileUrl: import.meta.url,
19
+ env: process.env,
20
+ });
21
+
22
+ const { pumukiPackageRoot, smokeCwd, binStrategy, binRoot } = layout;
23
+ const node = process.execPath;
24
+ const binPath = (name: string): string => join(binRoot, 'bin', name);
25
+
26
+ const run = (row: SmokeRow): { code: number | null; signal: NodeJS.Signals | null; ms: number } => {
27
+ const started = Date.now();
28
+ const r = spawnSync(node, [binPath(row.bin), ...row.args], {
29
+ cwd: smokeCwd,
30
+ encoding: 'utf8',
31
+ timeout: row.timeoutMs,
32
+ maxBuffer: 50 * 1024 * 1024,
33
+ env: { ...process.env, FORCE_COLOR: '0' },
34
+ });
35
+ return { code: r.status, signal: r.signal, ms: Date.now() - started };
36
+ };
37
+
38
+ const rows: ReadonlyArray<SmokeRow> = [
39
+ { id: 'cli.help_implicit', bin: 'pumuki.js', args: [], timeoutMs: 15_000, allowNonZero: true },
40
+ { id: 'cli.help_explicit', bin: 'pumuki.js', args: ['--help'], timeoutMs: 15_000 },
41
+ {
42
+ id: 'cli.unknown_subcommand',
43
+ bin: 'pumuki.js',
44
+ args: ['__no_such_lifecycle_command__'],
45
+ timeoutMs: 15_000,
46
+ allowNonZero: true,
47
+ },
48
+ { id: 'doctor.json', bin: 'pumuki.js', args: ['doctor', '--json'], timeoutMs: 60_000 },
49
+ {
50
+ id: 'doctor.deep_json',
51
+ bin: 'pumuki.js',
52
+ args: ['doctor', '--deep', '--json'],
53
+ timeoutMs: 180_000,
54
+ allowNonZero: true,
55
+ },
56
+ { id: 'doctor.parity_json', bin: 'pumuki.js', args: ['doctor', '--parity', '--json'], timeoutMs: 120_000 },
57
+ { id: 'status.json', bin: 'pumuki.js', args: ['status', '--json'], timeoutMs: 90_000 },
58
+ {
59
+ id: 'audit.default_json',
60
+ bin: 'pumuki.js',
61
+ args: ['audit', '--json'],
62
+ timeoutMs: 300_000,
63
+ allowNonZero: true,
64
+ },
65
+ {
66
+ id: 'audit.pre_push_json',
67
+ bin: 'pumuki.js',
68
+ args: ['audit', '--stage=PRE_PUSH', '--json'],
69
+ timeoutMs: 300_000,
70
+ allowNonZero: true,
71
+ },
72
+ {
73
+ id: 'audit.ci_engine_json',
74
+ bin: 'pumuki.js',
75
+ args: ['audit', '--stage=CI', '--engine', '--json'],
76
+ timeoutMs: 300_000,
77
+ allowNonZero: true,
78
+ },
79
+ {
80
+ id: 'watch.once_json',
81
+ bin: 'pumuki.js',
82
+ args: [
83
+ 'watch',
84
+ '--once',
85
+ '--json',
86
+ '--stage=PRE_COMMIT',
87
+ '--scope=repo',
88
+ '--interval-ms=200',
89
+ '--no-notify',
90
+ ],
91
+ timeoutMs: 180_000,
92
+ },
93
+ {
94
+ id: 'watch.once_staged_json',
95
+ bin: 'pumuki.js',
96
+ args: [
97
+ 'watch',
98
+ '--once',
99
+ '--json',
100
+ '--stage=PRE_COMMIT',
101
+ '--scope=staged',
102
+ '--interval-ms=200',
103
+ '--no-notify',
104
+ ],
105
+ timeoutMs: 180_000,
106
+ allowNonZero: true,
107
+ },
108
+ { id: 'loop.list_json', bin: 'pumuki.js', args: ['loop', 'list', '--json'], timeoutMs: 30_000 },
109
+ {
110
+ id: 'loop.run_smoke',
111
+ bin: 'pumuki.js',
112
+ args: ['loop', 'run', '--objective=smoke-surface', '--max-attempts=1', '--json'],
113
+ timeoutMs: 300_000,
114
+ allowNonZero: true,
115
+ },
116
+ {
117
+ id: 'adapter.install_dry_repo_json',
118
+ bin: 'pumuki.js',
119
+ args: ['adapter', 'install', '--agent=repo', '--dry-run', '--json'],
120
+ timeoutMs: 30_000,
121
+ },
122
+ {
123
+ id: 'adapter.install_dry_codex_json',
124
+ bin: 'pumuki.js',
125
+ args: ['adapter', 'install', '--agent=codex', '--dry-run', '--json'],
126
+ timeoutMs: 30_000,
127
+ },
128
+ {
129
+ id: 'analytics.hotspots_report',
130
+ bin: 'pumuki.js',
131
+ args: ['analytics', 'hotspots', 'report', '--top=3', '--since-days=30', '--json'],
132
+ timeoutMs: 120_000,
133
+ allowNonZero: true,
134
+ },
135
+ {
136
+ id: 'analytics.hotspots_diagnose',
137
+ bin: 'pumuki.js',
138
+ args: ['analytics', 'hotspots', 'diagnose', '--json'],
139
+ timeoutMs: 120_000,
140
+ allowNonZero: true,
141
+ },
142
+ {
143
+ id: 'policy.reconcile_json',
144
+ bin: 'pumuki.js',
145
+ args: ['policy', 'reconcile', '--json'],
146
+ timeoutMs: 120_000,
147
+ allowNonZero: true,
148
+ },
149
+ {
150
+ id: 'policy.reconcile_strict_json',
151
+ bin: 'pumuki.js',
152
+ args: ['policy', 'reconcile', '--strict', '--json'],
153
+ timeoutMs: 120_000,
154
+ allowNonZero: true,
155
+ },
156
+ { id: 'sdd.status_json', bin: 'pumuki.js', args: ['sdd', 'status', '--json'], timeoutMs: 60_000 },
157
+ {
158
+ id: 'sdd.validate_pre_write_json',
159
+ bin: 'pumuki.js',
160
+ args: ['sdd', 'validate', '--stage=PRE_WRITE', '--json'],
161
+ timeoutMs: 120_000,
162
+ allowNonZero: true,
163
+ },
164
+ {
165
+ id: 'sdd.validate_precommit_json',
166
+ bin: 'pumuki.js',
167
+ args: ['sdd', 'validate', '--stage=PRE_COMMIT', '--json'],
168
+ timeoutMs: 120_000,
169
+ allowNonZero: true,
170
+ },
171
+ {
172
+ id: 'sdd.validate_prepush_json',
173
+ bin: 'pumuki.js',
174
+ args: ['sdd', 'validate', '--stage=PRE_PUSH', '--json'],
175
+ timeoutMs: 120_000,
176
+ allowNonZero: true,
177
+ },
178
+ {
179
+ id: 'sdd.validate_ci_json',
180
+ bin: 'pumuki.js',
181
+ args: ['sdd', 'validate', '--stage=CI', '--json'],
182
+ timeoutMs: 120_000,
183
+ allowNonZero: true,
184
+ },
185
+ { id: 'hook.pre_write', bin: 'pumuki-pre-write.js', args: [], timeoutMs: 300_000, allowNonZero: true },
186
+ { id: 'hook.pre_commit', bin: 'pumuki-pre-commit.js', args: [], timeoutMs: 300_000, allowNonZero: true },
187
+ { id: 'hook.pre_push', bin: 'pumuki-pre-push.js', args: [], timeoutMs: 300_000, allowNonZero: true },
188
+ { id: 'hook.ci', bin: 'pumuki-ci.js', args: [], timeoutMs: 300_000, allowNonZero: true },
189
+ ];
190
+
191
+ const main = (): number => {
192
+ if (binStrategy === 'installed') {
193
+ const marker = installedBinMarkerPath(layout);
194
+ if (!existsSync(marker)) {
195
+ process.stderr.write(
196
+ `[pumuki] smoke: PUMUKI_SMOKE_BIN_STRATEGY=installed pero no existe ${marker}. ` +
197
+ 'Ejecuta npm install en el consumidor o revisa PUMUKI_SMOKE_REPO_ROOT.\n'
198
+ );
199
+ return 2;
200
+ }
201
+ }
202
+
203
+ const lines: string[] = [];
204
+ lines.push('# Pumuki superficie — smoke');
205
+ lines.push('');
206
+ lines.push(`- binStrategy: \`${binStrategy}\` (PUMUKI_SMOKE_BIN_STRATEGY=source|installed)`);
207
+ lines.push(`- binRoot: \`${binRoot}\``);
208
+ lines.push(`- pumukiPackageRoot (script host): \`${pumukiPackageRoot}\``);
209
+ lines.push(`- smokeCwd (gate/doctor cwd): \`${smokeCwd}\``);
210
+ lines.push(`- node: \`${node}\``);
211
+ lines.push(`- filas: **${rows.length}** (incluye CLI, doctor/status, audit×3, watch×2, loop, adapter×2, analytics×2, policy×2, sdd×5, hooks×4)`);
212
+ lines.push('');
213
+ lines.push('| id | exit | ms | ok |');
214
+ lines.push('|----|------|----|----|');
215
+ let failed = 0;
216
+ for (const row of rows) {
217
+ const { code, signal, ms } = run(row);
218
+ const exit = signal ? `signal:${signal}` : String(code ?? 'null');
219
+ const ok =
220
+ signal !== null
221
+ ? false
222
+ : row.allowNonZero
223
+ ? true
224
+ : code === 0;
225
+ if (!ok) {
226
+ failed += 1;
227
+ }
228
+ lines.push(`| ${row.id} | ${exit} | ${ms} | ${ok ? 'yes' : 'no'} |`);
229
+ }
230
+ lines.push('');
231
+ lines.push('## Interpretación de exit codes');
232
+ lines.push('');
233
+ lines.push('- `audit` devuelve el **mismo código que el gate** del alcance auditado (p. ej. 1 si hay `BLOCK` en el repo). Eso es correcto, no indica CLI roto.');
234
+ lines.push('- `doctor --deep` puede devolver **1** si `doctorHasBlockingIssues` (issues `error` o `deep.blocking`) o mismatch de parity esperado.');
235
+ lines.push('- Los **hooks** (`pre-commit`, etc.) reflejan el gate sobre el estado actual del worktree; 1 con cambios locales o rama protegida es esperable.');
236
+ lines.push('- `sdd validate` y `policy reconcile --strict` pueden devolver **1** según política del repo; el smoke los marca con `allowNonZero`.');
237
+ lines.push('');
238
+ lines.push('## No cubierto aquí (manual o destructivo)');
239
+ lines.push('');
240
+ lines.push('- `pumuki install|uninstall|remove|update|bootstrap` (mutan hooks/artefactos).');
241
+ lines.push('- `pumuki framework` / menú interactivo (`pumuki-framework.js`).');
242
+ lines.push('- Servidores MCP stdio (`pumuki-mcp-*-stdio.js`): esperan JSON-RPC por stdin; probar con cliente MCP.');
243
+ lines.push('- `pumuki sdd session|sync|learn|evidence|state-sync|auto-sync`: requieren ids / evidencia / cambios reales.');
244
+ lines.push('- `pumuki doctor|status --remote-checks`: dependen de red / GitHub.');
245
+ lines.push('');
246
+ lines.push('## Otro repo (consumidor)');
247
+ lines.push('');
248
+ lines.push(
249
+ '- **Bins del tree pumuki (desarrollo):** `PUMUKI_SMOKE_REPO_ROOT=/ruta/al/repo npm run -s smoke:pumuki-surface` desde la raíz de **pumuki** (`PUMUKI_SMOKE_BIN_STRATEGY=source` por defecto).'
250
+ );
251
+ lines.push(
252
+ '- **Bins instalados en el consumidor (`node_modules/pumuki`):** `PUMUKI_SMOKE_REPO_ROOT=/ruta/al/repo PUMUKI_SMOKE_BIN_STRATEGY=installed npm run -s smoke:pumuki-surface` o `npm run -s smoke:pumuki-surface-installed` (exige `PUMUKI_SMOKE_REPO_ROOT`).'
253
+ );
254
+ lines.push('');
255
+ lines.push(`**Resumen:** ${failed} filas con fallo estricto (sin allowNonZero).`);
256
+ process.stdout.write(lines.join('\n'));
257
+ process.stdout.write('\n');
258
+ return failed > 0 ? 1 : 0;
259
+ };
260
+
261
+ process.exitCode = main();
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const { spawnSync } = require('node:child_process');
5
+ const { join } = require('node:path');
6
+
7
+ if (!process.env.PUMUKI_SMOKE_REPO_ROOT || !String(process.env.PUMUKI_SMOKE_REPO_ROOT).trim()) {
8
+ console.error(
9
+ '[pumuki] smoke:pumuki-surface-installed requires PUMUKI_SMOKE_REPO_ROOT (absolute path to consumer repo).'
10
+ );
11
+ process.exit(1);
12
+ }
13
+
14
+ const root = join(__dirname, '..');
15
+ const env = {
16
+ ...process.env,
17
+ PUMUKI_SMOKE_BIN_STRATEGY: 'installed',
18
+ };
19
+
20
+ const result = spawnSync(
21
+ 'npx',
22
+ ['--yes', 'tsx@4.21.0', 'scripts/pumuki-full-surface-smoke.ts'],
23
+ {
24
+ cwd: root,
25
+ env,
26
+ stdio: 'inherit',
27
+ }
28
+ );
29
+
30
+ const code = typeof result.status === 'number' ? result.status : 1;
31
+ process.exitCode = code;