pumuki 6.3.64 → 6.3.68
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 +1 -1
- package/docs/operations/RELEASE_NOTES.md +17 -0
- package/docs/product/INSTALLATION.md +2 -0
- package/integrations/lifecycle/adapter.templates.json +57 -0
- package/integrations/lifecycle/hookBlock.ts +99 -42
- package/integrations/lifecycle/install.ts +20 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -37,7 +37,7 @@ npx --yes pumuki status
|
|
|
37
37
|
npx --yes pumuki doctor --json
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
Desde **6.3.63**, `npm install` en la raíz de un repo **Git** dispara un `postinstall` que ejecuta
|
|
40
|
+
Desde **6.3.63**, `npm install` en la raíz de un repo **Git** dispara un `postinstall` que ejecuta **`pumuki install` solo** (hooks `pre-commit` / `pre-push`, lifecycle, evidencia cuando aplica). **Pumuki no depende de ningún IDE** para el baseline: no toca `.cursor/` ni otros ficheros de editor por defecto. Desde **6.3.68**, cada hook gestionado ejecuta **`pumuki-pre-write` antes** de `pumuki-pre-commit` / `pumuki-pre-push` (stage **PRE_WRITE** vía Git). Saltar solo PRE_WRITE en hooks: `PUMUKI_SKIP_CHAINED_PRE_WRITE=1`. Tras install, si no existía, aparece **`.pumuki/adapter.json`** con los comandos de hooks y **MCP stdio** para referencia o para clientes que los registren manualmente. Para generar también config de IDE: **`pumuki install --with-mcp --agent=cursor`** (u otro) o **`pumuki bootstrap --enterprise --agent=…`**. Desactivar el postinstall: `PUMUKI_SKIP_POSTINSTALL=1`. En CI suele saltarse solo (`CI=true`). En **6.3.64+**, las notificaciones del sistema en plataformas sin banner nativo se reflejan en **stderr** por defecto (`PUMUKI_DISABLE_STDERR_NOTIFICATIONS=1` para silenciarlas).
|
|
41
41
|
|
|
42
42
|
Fallback (equivalent in pasos separados):
|
|
43
43
|
|
|
@@ -6,6 +6,23 @@ This file keeps only the operational highlights and rollout notes that matter wh
|
|
|
6
6
|
|
|
7
7
|
## 2026-04 (CLI stability and macOS notifications)
|
|
8
8
|
|
|
9
|
+
### 2026-04-06 (v6.3.68)
|
|
10
|
+
|
|
11
|
+
- **PRE_WRITE sin depender del IDE**: `pre-commit` y `pre-push` gestionados ejecutan **`pumuki-pre-write`** antes del gate del stage principal. Opt-out: `PUMUKI_SKIP_CHAINED_PRE_WRITE=1`.
|
|
12
|
+
- **`.pumuki/adapter.json`**: se crea en `pumuki install` si faltaba, con comandos de **MCP stdio** y hooks (plantilla `repo`); sigue sin imponer Cursor ni otros IDEs.
|
|
13
|
+
- Rollout: `pumuki@6.3.68` y **`pumuki install`** en consumidores para reescribir hooks.
|
|
14
|
+
|
|
15
|
+
### 2026-04-06 (v6.3.67)
|
|
16
|
+
|
|
17
|
+
- **Corrección de producto**: el `postinstall` **no** acopla Pumuki a Cursor ni a ningún IDE. Vuelve a ejecutar solo **`pumuki install`** (baseline Git + lifecycle). IDE/MCP: opt-in con `pumuki install --with-mcp --agent=…` o `bootstrap --enterprise`.
|
|
18
|
+
- La plantilla adaptador **Cursor** sigue pudiendo fusionar `.cursor/mcp.json` y escribir `.pumuki/adapter.json` cuando el equipo elige ese agente explícitamente.
|
|
19
|
+
- Rollout: `pumuki@6.3.67` si **6.3.66** llegó a publicarse; si no, repin directo a **6.3.67**.
|
|
20
|
+
|
|
21
|
+
### 2026-04-06 (v6.3.65)
|
|
22
|
+
|
|
23
|
+
- **pre-commit.com + `exec`**: Pumuki ya no inserta su bloque gestionado *después* del `exec` del hook generado por pre-commit (código inalcanzable). Tras actualizar a **6.3.65**, ejecutar `pumuki install` en el consumer para reordenar `.git/hooks/pre-commit`.
|
|
24
|
+
- Rollout: repin a `pumuki@6.3.65`; validar que un `git commit` dispara el gate (salida `[pumuki]` / policy) antes de que corra pre-commit.
|
|
25
|
+
|
|
9
26
|
### 2026-04-05 (v6.3.64)
|
|
10
27
|
|
|
11
28
|
- **Notificaciones multiplataforma**: fuera de macOS, avisos críticos van a **stderr** por defecto (terminal visible). `PUMUKI_DISABLE_STDERR_NOTIFICATIONS=1` lo silencia para CI/scripts. En macOS, `PUMUKI_NOTIFICATION_STDERR_MIRROR=1` añade copia en terminal; si `osascript`/banner falla, stderr actúa como respaldo.
|
|
@@ -51,6 +51,8 @@ If both commands pass, the workspace is ready.
|
|
|
51
51
|
npm install --save-exact pumuki
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
+
The package `postinstall` runs **`pumuki install` only** (Git hooks + lifecycle wiring). Hooks chain **`pumuki-pre-write`** then **`pumuki-pre-commit`** / **`pumuki-pre-push`** (IDE-agnostic). When missing, **`.pumuki/adapter.json`** is created with hook + **MCP stdio** command lines (any MCP client can use them). It does **not** write IDE-specific files; use step 2 or `pumuki install --with-mcp --agent=<name>` for that.
|
|
55
|
+
|
|
54
56
|
### 2) Bootstrap managed lifecycle (recommended single command)
|
|
55
57
|
|
|
56
58
|
```bash
|
|
@@ -28,6 +28,35 @@
|
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
],
|
|
31
|
+
"repo": [
|
|
32
|
+
{
|
|
33
|
+
"path": ".pumuki/adapter.json",
|
|
34
|
+
"payload": {
|
|
35
|
+
"hooks": {
|
|
36
|
+
"pre_write": {
|
|
37
|
+
"command": "npx --yes --package pumuki@latest pumuki-pre-write"
|
|
38
|
+
},
|
|
39
|
+
"pre_commit": {
|
|
40
|
+
"command": "npx --yes --package pumuki@latest pumuki-pre-commit"
|
|
41
|
+
},
|
|
42
|
+
"pre_push": {
|
|
43
|
+
"command": "npx --yes --package pumuki@latest pumuki-pre-push"
|
|
44
|
+
},
|
|
45
|
+
"ci": {
|
|
46
|
+
"command": "npx --yes --package pumuki@latest pumuki-ci"
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"mcp": {
|
|
50
|
+
"enterprise": {
|
|
51
|
+
"command": "npx --yes --package pumuki@latest pumuki-mcp-enterprise-stdio"
|
|
52
|
+
},
|
|
53
|
+
"evidence": {
|
|
54
|
+
"command": "npx --yes --package pumuki@latest pumuki-mcp-evidence-stdio"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
],
|
|
31
60
|
"claude": [
|
|
32
61
|
{
|
|
33
62
|
"path": ".claude/settings.json",
|
|
@@ -74,6 +103,7 @@
|
|
|
74
103
|
"cursor": [
|
|
75
104
|
{
|
|
76
105
|
"path": ".cursor/mcp.json",
|
|
106
|
+
"mode": "json-merge",
|
|
77
107
|
"payload": {
|
|
78
108
|
"mcpServers": {
|
|
79
109
|
"pumuki-enterprise": {
|
|
@@ -110,6 +140,33 @@
|
|
|
110
140
|
}
|
|
111
141
|
}
|
|
112
142
|
}
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
"path": ".pumuki/adapter.json",
|
|
146
|
+
"payload": {
|
|
147
|
+
"hooks": {
|
|
148
|
+
"pre_write": {
|
|
149
|
+
"command": "npx --yes --package pumuki@latest pumuki-pre-write"
|
|
150
|
+
},
|
|
151
|
+
"pre_commit": {
|
|
152
|
+
"command": "npx --yes --package pumuki@latest pumuki-pre-commit"
|
|
153
|
+
},
|
|
154
|
+
"pre_push": {
|
|
155
|
+
"command": "npx --yes --package pumuki@latest pumuki-pre-push"
|
|
156
|
+
},
|
|
157
|
+
"ci": {
|
|
158
|
+
"command": "npx --yes --package pumuki@latest pumuki-ci"
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
"mcp": {
|
|
162
|
+
"enterprise": {
|
|
163
|
+
"command": "npx --yes --package pumuki@latest pumuki-mcp-enterprise-stdio"
|
|
164
|
+
},
|
|
165
|
+
"evidence": {
|
|
166
|
+
"command": "npx --yes --package pumuki@latest pumuki-mcp-evidence-stdio"
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
113
170
|
}
|
|
114
171
|
],
|
|
115
172
|
"windsurf": [
|
|
@@ -9,6 +9,10 @@ const HOOK_COMMANDS: Record<PumukiManagedHook, string> = {
|
|
|
9
9
|
'pre-push': 'pumuki-pre-push',
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
+
const PRE_WRITE_CLI = 'pumuki-pre-write';
|
|
13
|
+
|
|
14
|
+
type ResolverPhase = 'pre-write' | 'main';
|
|
15
|
+
|
|
12
16
|
const trimTrailingWhitespace = (value: string): string =>
|
|
13
17
|
value.replace(/[ \t]+\n/g, '\n').trimEnd();
|
|
14
18
|
|
|
@@ -24,54 +28,72 @@ const managedBlockPattern = new RegExp(
|
|
|
24
28
|
'g'
|
|
25
29
|
);
|
|
26
30
|
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
const runnerLine = (
|
|
32
|
+
parentHook: PumukiManagedHook,
|
|
33
|
+
phase: ResolverPhase,
|
|
34
|
+
runner: string
|
|
35
|
+
): string => {
|
|
36
|
+
if (parentHook === 'pre-push' && phase === 'main') {
|
|
37
|
+
return ` PUMUKI_PRE_PUSH_STDIN="$PUMUKI_PRE_PUSH_STDIN" ${runner} "$@"`;
|
|
33
38
|
}
|
|
34
|
-
return ` ${
|
|
39
|
+
return ` ${runner}`;
|
|
35
40
|
};
|
|
36
41
|
|
|
37
|
-
|
|
38
|
-
|
|
42
|
+
const buildCliResolver = (params: {
|
|
43
|
+
cli: string;
|
|
44
|
+
parentHook: PumukiManagedHook;
|
|
45
|
+
phase: ResolverPhase;
|
|
46
|
+
}): string[] => {
|
|
47
|
+
const { cli, parentHook, phase } = params;
|
|
39
48
|
const localBinPath = `./node_modules/.bin/${cli}`;
|
|
40
49
|
const localNodeEntry = `./node_modules/pumuki/bin/${cli}.js`;
|
|
41
|
-
|
|
42
50
|
return [
|
|
43
|
-
PUMUKI_MANAGED_BLOCK_START,
|
|
44
|
-
...(hook === 'pre-push' ? ['PUMUKI_PRE_PUSH_STDIN="$(cat)"'] : []),
|
|
45
51
|
`if [ -x "${localBinPath}" ]; then`,
|
|
46
|
-
|
|
47
|
-
hook,
|
|
48
|
-
runner: localBinPath,
|
|
49
|
-
}),
|
|
52
|
+
runnerLine(parentHook, phase, localBinPath),
|
|
50
53
|
`elif [ -f "${localNodeEntry}" ] && command -v node >/dev/null 2>&1; then`,
|
|
51
|
-
|
|
52
|
-
hook,
|
|
53
|
-
runner: `node ${localNodeEntry}`,
|
|
54
|
-
}),
|
|
54
|
+
runnerLine(parentHook, phase, `node ${localNodeEntry}`),
|
|
55
55
|
`elif command -v ${cli} >/dev/null 2>&1; then`,
|
|
56
|
-
|
|
57
|
-
hook,
|
|
58
|
-
runner: cli,
|
|
59
|
-
}),
|
|
56
|
+
runnerLine(parentHook, phase, cli),
|
|
60
57
|
'elif command -v npx >/dev/null 2>&1; then',
|
|
61
|
-
|
|
62
|
-
hook,
|
|
63
|
-
runner: `npx --yes --package pumuki@latest ${cli}`,
|
|
64
|
-
}),
|
|
58
|
+
runnerLine(parentHook, phase, `npx --yes --package pumuki@latest ${cli}`),
|
|
65
59
|
'else',
|
|
66
60
|
` echo "[pumuki] unable to resolve ${cli} runner. Install dependencies or ensure npx is available." >&2`,
|
|
67
61
|
' exit 1',
|
|
68
62
|
'fi',
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
]
|
|
63
|
+
];
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export const buildPumukiManagedHookBlock = (hook: PumukiManagedHook): string => {
|
|
67
|
+
const mainCli = HOOK_COMMANDS[hook];
|
|
68
|
+
const lines: string[] = [PUMUKI_MANAGED_BLOCK_START];
|
|
69
|
+
|
|
70
|
+
lines.push('if [ "${PUMUKI_SKIP_CHAINED_PRE_WRITE:-}" != "1" ]; then');
|
|
71
|
+
lines.push(...buildCliResolver({
|
|
72
|
+
cli: PRE_WRITE_CLI,
|
|
73
|
+
parentHook: hook,
|
|
74
|
+
phase: 'pre-write',
|
|
75
|
+
}));
|
|
76
|
+
lines.push(' status=$?');
|
|
77
|
+
lines.push(' if [ "$status" -ne 0 ]; then');
|
|
78
|
+
lines.push(' exit "$status"');
|
|
79
|
+
lines.push(' fi');
|
|
80
|
+
lines.push('fi');
|
|
81
|
+
|
|
82
|
+
if (hook === 'pre-push') {
|
|
83
|
+
lines.push('PUMUKI_PRE_PUSH_STDIN="$(cat)"');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
lines.push(...buildCliResolver({
|
|
87
|
+
cli: mainCli,
|
|
88
|
+
parentHook: hook,
|
|
89
|
+
phase: 'main',
|
|
90
|
+
}));
|
|
91
|
+
lines.push(' status=$?');
|
|
92
|
+
lines.push(' if [ "$status" -ne 0 ]; then');
|
|
93
|
+
lines.push(' exit "$status"');
|
|
94
|
+
lines.push(' fi');
|
|
95
|
+
lines.push(PUMUKI_MANAGED_BLOCK_END);
|
|
96
|
+
return lines.join('\n');
|
|
75
97
|
};
|
|
76
98
|
|
|
77
99
|
const ensureExecutableHeader = (contents: string): string => {
|
|
@@ -82,23 +104,58 @@ const ensureExecutableHeader = (contents: string): string => {
|
|
|
82
104
|
return contents;
|
|
83
105
|
};
|
|
84
106
|
|
|
107
|
+
const isPreCommitFrameworkWithExecTerminator = (contents: string): boolean => {
|
|
108
|
+
if (!contents.includes('pre-commit.com') && !contents.includes('pre_commit')) {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
return /\bexec\b/.test(contents);
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const prependManagedBlockAfterShebang = (params: {
|
|
115
|
+
contents: string;
|
|
116
|
+
block: string;
|
|
117
|
+
}): string => {
|
|
118
|
+
const trimmed = trimTrailingWhitespace(params.contents);
|
|
119
|
+
const firstNewline = trimmed.indexOf('\n');
|
|
120
|
+
const shebangLine = firstNewline === -1 ? trimmed : trimmed.slice(0, firstNewline);
|
|
121
|
+
const rest =
|
|
122
|
+
firstNewline === -1 ? '' : trimmed.slice(firstNewline + 1).replace(/^\n+/, '');
|
|
123
|
+
const header = shebangLine.startsWith('#!') ? shebangLine : '#!/usr/bin/env sh';
|
|
124
|
+
const bodyAfterShebang = shebangLine.startsWith('#!') ? rest : trimmed;
|
|
125
|
+
const tail = trimTrailingWhitespace(bodyAfterShebang);
|
|
126
|
+
return tail.length > 0
|
|
127
|
+
? `${header}\n${params.block}\n\n${tail}\n`
|
|
128
|
+
: `${header}\n${params.block}\n`;
|
|
129
|
+
};
|
|
130
|
+
|
|
85
131
|
export const upsertPumukiManagedBlock = (params: {
|
|
86
132
|
contents: string;
|
|
87
133
|
hook: PumukiManagedHook;
|
|
88
134
|
}): string => {
|
|
89
135
|
const block = buildPumukiManagedHookBlock(params.hook);
|
|
90
136
|
const baseline = ensureExecutableHeader(params.contents);
|
|
137
|
+
const hadManagedBlock = hasPumukiManagedBlock(baseline);
|
|
138
|
+
const core = collapseBlankLines(
|
|
139
|
+
trimTrailingWhitespace(
|
|
140
|
+
hadManagedBlock ? baseline.replace(managedBlockPattern, '\n') : baseline
|
|
141
|
+
)
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
if (
|
|
145
|
+
params.hook === 'pre-commit' &&
|
|
146
|
+
core.length > 0 &&
|
|
147
|
+
isPreCommitFrameworkWithExecTerminator(core)
|
|
148
|
+
) {
|
|
149
|
+
const merged = prependManagedBlockAfterShebang({ contents: core, block });
|
|
150
|
+
return `${trimTrailingWhitespace(merged)}\n`;
|
|
151
|
+
}
|
|
91
152
|
|
|
92
|
-
if (
|
|
93
|
-
const
|
|
94
|
-
return `${trimTrailingWhitespace(
|
|
153
|
+
if (hadManagedBlock || core.length > 0) {
|
|
154
|
+
const withSeparator = core.length > 0 ? `${core}\n\n${block}` : block;
|
|
155
|
+
return `${trimTrailingWhitespace(withSeparator)}\n`;
|
|
95
156
|
}
|
|
96
157
|
|
|
97
|
-
|
|
98
|
-
trimTrailingWhitespace(baseline).length > 0
|
|
99
|
-
? `${trimTrailingWhitespace(baseline)}\n\n${block}`
|
|
100
|
-
: block;
|
|
101
|
-
return `${withSeparator}\n`;
|
|
158
|
+
return `${block}\n`;
|
|
102
159
|
};
|
|
103
160
|
|
|
104
161
|
export const removePumukiManagedBlock = (contents: string): {
|
|
@@ -12,6 +12,7 @@ import { captureRepoState } from '../evidence/repoState';
|
|
|
12
12
|
import { createEmptyEvaluationMetrics } from '../evidence/evaluationMetrics';
|
|
13
13
|
import { readOpenSpecManagedArtifacts, writeLifecycleState } from './state';
|
|
14
14
|
import { ensureRuntimeArtifactsIgnored } from './artifacts';
|
|
15
|
+
import { runLifecycleAdapterInstall } from './adapter';
|
|
15
16
|
|
|
16
17
|
export type LifecycleInstallResult = {
|
|
17
18
|
repoRoot: string;
|
|
@@ -59,6 +60,23 @@ const wireHooksLifecycleAndBootstrapEvidence = (params: {
|
|
|
59
60
|
return hookResult.changedHooks;
|
|
60
61
|
};
|
|
61
62
|
|
|
63
|
+
const ensureRepoBaselineAdapter = (repoRoot: string): void => {
|
|
64
|
+
const adapterPath = join(repoRoot, '.pumuki', 'adapter.json');
|
|
65
|
+
if (existsSync(adapterPath)) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
runLifecycleAdapterInstall({
|
|
70
|
+
cwd: repoRoot,
|
|
71
|
+
agent: 'repo',
|
|
72
|
+
});
|
|
73
|
+
} catch (cause: unknown) {
|
|
74
|
+
if (process.env.PUMUKI_VERBOSE_INSTALL === '1') {
|
|
75
|
+
console.debug('[pumuki] adapter scaffold skipped', cause);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
62
80
|
export const runLifecycleInstall = (params?: {
|
|
63
81
|
cwd?: string;
|
|
64
82
|
git?: ILifecycleGitService;
|
|
@@ -84,6 +102,7 @@ export const runLifecycleInstall = (params?: {
|
|
|
84
102
|
version,
|
|
85
103
|
openSpecManagedArtifacts: priorArtifacts.length > 0 ? priorArtifacts : undefined,
|
|
86
104
|
});
|
|
105
|
+
ensureRepoBaselineAdapter(report.repoRoot);
|
|
87
106
|
return {
|
|
88
107
|
repoRoot: report.repoRoot,
|
|
89
108
|
version,
|
|
@@ -122,6 +141,7 @@ export const runLifecycleInstall = (params?: {
|
|
|
122
141
|
version,
|
|
123
142
|
openSpecManagedArtifacts: Array.from(mergedOpenSpecArtifacts),
|
|
124
143
|
});
|
|
144
|
+
ensureRepoBaselineAdapter(report.repoRoot);
|
|
125
145
|
|
|
126
146
|
return {
|
|
127
147
|
repoRoot: report.repoRoot,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pumuki",
|
|
3
|
-
"version": "6.3.
|
|
3
|
+
"version": "6.3.68",
|
|
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": {
|
|
@@ -212,6 +212,7 @@
|
|
|
212
212
|
"@babel/preset-env": "^7.29.0",
|
|
213
213
|
"@babel/preset-typescript": "^7.28.5",
|
|
214
214
|
"@babel/traverse": "^7.28.5",
|
|
215
|
+
"@fission-ai/openspec": "1.2.0",
|
|
215
216
|
"@types/node": "^20.10.0",
|
|
216
217
|
"@typescript-eslint/eslint-plugin": "^8.55.0",
|
|
217
218
|
"@typescript-eslint/parser": "^8.55.0",
|