pumuki 6.3.374 → 6.3.375
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/integrations/lifecycle/cli.ts +14 -2
- package/integrations/lifecycle/cliSdd.ts +34 -0
- package/integrations/lifecycle/preWriteLease.ts +60 -0
- package/package.json +1 -1
- package/scripts/package-install-smoke-execution-lib.ts +5 -1
- package/scripts/package-install-smoke-execution-steps-lib.ts +59 -0
|
@@ -104,6 +104,7 @@ type LifecycleCommand =
|
|
|
104
104
|
type SddCommand =
|
|
105
105
|
| 'status'
|
|
106
106
|
| 'validate'
|
|
107
|
+
| 'guard'
|
|
107
108
|
| 'session'
|
|
108
109
|
| 'sync-docs'
|
|
109
110
|
| 'learn'
|
|
@@ -219,6 +220,7 @@ Pumuki lifecycle commands:
|
|
|
219
220
|
pumuki context repair [--json]
|
|
220
221
|
pumuki sdd status [--json]
|
|
221
222
|
pumuki sdd validate [--stage=PRE_WRITE|PRE_COMMIT|PRE_PUSH|CI] [--json]
|
|
223
|
+
pumuki sdd guard [--stage=PRE_WRITE] [--json]
|
|
222
224
|
pumuki sdd session --open --change=<change-id|auto> [--ttl-minutes=<n>] [--json]
|
|
223
225
|
pumuki sdd session --refresh [--ttl-minutes=<n>] [--json]
|
|
224
226
|
pumuki sdd session --close [--json]
|
|
@@ -998,6 +1000,7 @@ export const parseLifecycleCliArgs = (argv: ReadonlyArray<string>): ParsedArgs =
|
|
|
998
1000
|
if (
|
|
999
1001
|
subcommandRaw !== 'status' &&
|
|
1000
1002
|
subcommandRaw !== 'validate' &&
|
|
1003
|
+
subcommandRaw !== 'guard' &&
|
|
1001
1004
|
subcommandRaw !== 'session' &&
|
|
1002
1005
|
subcommandRaw !== 'sync-docs' &&
|
|
1003
1006
|
subcommandRaw !== 'sync' &&
|
|
@@ -1039,7 +1042,7 @@ export const parseLifecycleCliArgs = (argv: ReadonlyArray<string>): ParsedArgs =
|
|
|
1039
1042
|
throw new Error(`--dry-run is only supported with "pumuki sdd sync-docs", "pumuki sdd learn", "pumuki sdd auto-sync", "pumuki sdd evidence" or "pumuki sdd state-sync".\n\n${HELP_TEXT}`);
|
|
1040
1043
|
}
|
|
1041
1044
|
if (arg.startsWith('--stage=')) {
|
|
1042
|
-
if (sddCommand === 'validate') {
|
|
1045
|
+
if (sddCommand === 'validate' || sddCommand === 'guard') {
|
|
1043
1046
|
sddStage = parseSddStage(arg.slice('--stage='.length));
|
|
1044
1047
|
continue;
|
|
1045
1048
|
}
|
|
@@ -1055,7 +1058,7 @@ export const parseLifecycleCliArgs = (argv: ReadonlyArray<string>): ParsedArgs =
|
|
|
1055
1058
|
sddAutoSyncStage = parseSddStage(arg.slice('--stage='.length));
|
|
1056
1059
|
continue;
|
|
1057
1060
|
}
|
|
1058
|
-
throw new Error(`--stage is only supported with "pumuki sdd validate", "pumuki sdd sync-docs", "pumuki sdd learn" or "pumuki sdd auto-sync".\n\n${HELP_TEXT}`);
|
|
1061
|
+
throw new Error(`--stage is only supported with "pumuki sdd validate", "pumuki sdd guard", "pumuki sdd sync-docs", "pumuki sdd learn" or "pumuki sdd auto-sync".\n\n${HELP_TEXT}`);
|
|
1059
1062
|
}
|
|
1060
1063
|
if (arg.startsWith('--status=')) {
|
|
1061
1064
|
if (sddCommand !== 'state-sync') {
|
|
@@ -1272,6 +1275,15 @@ export const parseLifecycleCliArgs = (argv: ReadonlyArray<string>): ParsedArgs =
|
|
|
1272
1275
|
sddStage: sddStage ?? 'PRE_COMMIT',
|
|
1273
1276
|
};
|
|
1274
1277
|
}
|
|
1278
|
+
if (sddCommand === 'guard') {
|
|
1279
|
+
return {
|
|
1280
|
+
command: commandRaw,
|
|
1281
|
+
purgeArtifacts: false,
|
|
1282
|
+
json,
|
|
1283
|
+
sddCommand,
|
|
1284
|
+
sddStage: sddStage ?? 'PRE_WRITE',
|
|
1285
|
+
};
|
|
1286
|
+
}
|
|
1275
1287
|
if (sddCommand === 'sync-docs') {
|
|
1276
1288
|
if (
|
|
1277
1289
|
sddSessionAction ||
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
} from '../policy/preWriteEnforcement';
|
|
23
23
|
import { readLifecycleExperimentalFeaturesSnapshot } from './experimentalFeaturesSnapshot';
|
|
24
24
|
import { GitService } from '../git/GitService';
|
|
25
|
+
import { evaluatePreWriteAgentGuard } from './preWriteLease';
|
|
25
26
|
|
|
26
27
|
import {
|
|
27
28
|
type ParsedArgs,
|
|
@@ -68,6 +69,39 @@ export const runSddCommand = async (parsed: ParsedArgs, activeDependencies: Life
|
|
|
68
69
|
}
|
|
69
70
|
return 0;
|
|
70
71
|
}
|
|
72
|
+
if (parsed.sddCommand === 'guard') {
|
|
73
|
+
const requestedStage = parsed.sddStage ?? 'PRE_WRITE';
|
|
74
|
+
if (requestedStage !== 'PRE_WRITE') {
|
|
75
|
+
if (parsed.json) {
|
|
76
|
+
writeInfo(JSON.stringify({
|
|
77
|
+
allowed: false,
|
|
78
|
+
stage: requestedStage,
|
|
79
|
+
code: 'PRE_WRITE_AGENT_GUARD_UNSUPPORTED_STAGE',
|
|
80
|
+
message: 'Agent pre-write guard only supports PRE_WRITE.',
|
|
81
|
+
}, null, 2));
|
|
82
|
+
} else {
|
|
83
|
+
writeInfo('[pumuki][sdd] guard allowed=no code=PRE_WRITE_AGENT_GUARD_UNSUPPORTED_STAGE');
|
|
84
|
+
writeInfo('[pumuki][sdd] Agent pre-write guard only supports PRE_WRITE.');
|
|
85
|
+
}
|
|
86
|
+
return 1;
|
|
87
|
+
}
|
|
88
|
+
const guard = evaluatePreWriteAgentGuard({
|
|
89
|
+
repoRoot: process.cwd(),
|
|
90
|
+
git: new GitService(),
|
|
91
|
+
});
|
|
92
|
+
if (parsed.json) {
|
|
93
|
+
writeInfo(JSON.stringify(guard, null, 2));
|
|
94
|
+
} else {
|
|
95
|
+
writeInfo(
|
|
96
|
+
`[pumuki][sdd] guard stage=PRE_WRITE allowed=${guard.allowed ? 'yes' : 'no'} code=${guard.code}`
|
|
97
|
+
);
|
|
98
|
+
writeInfo(`[pumuki][sdd] ${guard.message}`);
|
|
99
|
+
if (!guard.allowed) {
|
|
100
|
+
writeInfo(`[pumuki][sdd] solution: ${guard.expected_fix}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return guard.allowed ? 0 : 1;
|
|
104
|
+
}
|
|
71
105
|
if (parsed.sddCommand === 'validate') {
|
|
72
106
|
const requestedStage = parsed.sddStage ?? 'PRE_COMMIT';
|
|
73
107
|
const preWriteEnforcement = requestedStage === 'PRE_WRITE'
|
|
@@ -50,6 +50,24 @@ export type PreWriteLeaseWriteResult = {
|
|
|
50
50
|
changedCodePaths: string[];
|
|
51
51
|
};
|
|
52
52
|
|
|
53
|
+
export type PreWriteAgentGuardResult = {
|
|
54
|
+
allowed: boolean;
|
|
55
|
+
stage: 'PRE_WRITE';
|
|
56
|
+
code:
|
|
57
|
+
| 'PRE_WRITE_AGENT_GUARD_ALLOWED'
|
|
58
|
+
| 'PRE_WRITE_AGENT_GUARD_LEASE_MISSING'
|
|
59
|
+
| 'PRE_WRITE_AGENT_GUARD_LEASE_INVALID'
|
|
60
|
+
| 'PRE_WRITE_AGENT_GUARD_LEASE_EXPIRED'
|
|
61
|
+
| 'PRE_WRITE_AGENT_GUARD_LEASE_HEAD_MISMATCH'
|
|
62
|
+
| 'PRE_WRITE_AGENT_GUARD_LEASE_DIRTY_AT_ISSUE'
|
|
63
|
+
| 'PRE_WRITE_AGENT_GUARD_CODE_CHANGED_WITHOUT_LEASE';
|
|
64
|
+
message: string;
|
|
65
|
+
expected_fix: string;
|
|
66
|
+
path: string;
|
|
67
|
+
changedCodePaths: string[];
|
|
68
|
+
lease_status: PreWriteLeaseStatus;
|
|
69
|
+
};
|
|
70
|
+
|
|
53
71
|
const PRE_WRITE_LEASE_TTL_MS = 4 * 60 * 60 * 1000;
|
|
54
72
|
|
|
55
73
|
export const resolvePreWriteLeasePath = (repoRoot: string): string =>
|
|
@@ -287,6 +305,48 @@ export const writePreWriteLease = (params: {
|
|
|
287
305
|
};
|
|
288
306
|
};
|
|
289
307
|
|
|
308
|
+
export const evaluatePreWriteAgentGuard = (params: {
|
|
309
|
+
repoRoot: string;
|
|
310
|
+
git: Pick<IGitService, 'runGit'>;
|
|
311
|
+
now?: Date;
|
|
312
|
+
}): PreWriteAgentGuardResult => {
|
|
313
|
+
const leaseStatus = readPreWriteLeaseStatus(params);
|
|
314
|
+
if (leaseStatus.valid) {
|
|
315
|
+
return {
|
|
316
|
+
allowed: true,
|
|
317
|
+
stage: 'PRE_WRITE',
|
|
318
|
+
code: 'PRE_WRITE_AGENT_GUARD_ALLOWED',
|
|
319
|
+
message: 'PRE_WRITE agent guard passed. A valid PRE_WRITE lease exists for this repo and diff.',
|
|
320
|
+
expected_fix: 'Continue with the current slice; keep edits inside the validated scope.',
|
|
321
|
+
path: leaseStatus.path,
|
|
322
|
+
changedCodePaths: leaseStatus.changedCodePaths,
|
|
323
|
+
lease_status: leaseStatus,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const hasCodeChanges = leaseStatus.changedCodePaths.length > 0;
|
|
328
|
+
const code = hasCodeChanges
|
|
329
|
+
? 'PRE_WRITE_AGENT_GUARD_CODE_CHANGED_WITHOUT_LEASE'
|
|
330
|
+
: (`PRE_WRITE_AGENT_GUARD_${leaseStatus.code.replace('PRE_WRITE_', '')}` as PreWriteAgentGuardResult['code']);
|
|
331
|
+
const changedDetails = hasCodeChanges
|
|
332
|
+
? ` Code changes already exist: ${leaseStatus.changedCodePaths.join(', ')}.`
|
|
333
|
+
: '';
|
|
334
|
+
|
|
335
|
+
return {
|
|
336
|
+
allowed: false,
|
|
337
|
+
stage: 'PRE_WRITE',
|
|
338
|
+
code,
|
|
339
|
+
message:
|
|
340
|
+
`AI agent instruction: STOP; do not apply patches or write files until PRE_WRITE is valid. ` +
|
|
341
|
+
`${leaseStatus.message}${changedDetails}`,
|
|
342
|
+
expected_fix:
|
|
343
|
+
'run `npx pumuki-pre-write` before editing code. If code changes already exist, stop and ask whether to validate the current diff, stash it, or restart from a clean PRE_WRITE lease.',
|
|
344
|
+
path: leaseStatus.path,
|
|
345
|
+
changedCodePaths: leaseStatus.changedCodePaths,
|
|
346
|
+
lease_status: leaseStatus,
|
|
347
|
+
};
|
|
348
|
+
};
|
|
349
|
+
|
|
290
350
|
export const toPreWriteEnforcementGapFinding = (params: {
|
|
291
351
|
stage: 'PRE_COMMIT' | 'PRE_PUSH' | 'CI';
|
|
292
352
|
status: PreWriteLeaseStatus;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pumuki",
|
|
3
|
-
"version": "6.3.
|
|
3
|
+
"version": "6.3.375",
|
|
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": {
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type { SmokeMode } from './package-install-smoke-contract';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
runDefaultSmokeGateSteps,
|
|
4
|
+
runSmokeGoldenFlowEvidenceStep,
|
|
5
|
+
} from './package-install-smoke-execution-steps-lib';
|
|
3
6
|
import {
|
|
4
7
|
assertLifecycleStatusMatchesSnapshot,
|
|
5
8
|
captureLifecycleStatusSnapshot,
|
|
@@ -41,6 +44,7 @@ export const runPackageInstallSmoke = (mode: SmokeMode): void => {
|
|
|
41
44
|
runLifecycleInstallStep(workspace);
|
|
42
45
|
runLifecyclePreWriteStep(workspace);
|
|
43
46
|
writeStagedPayload(workspace, mode);
|
|
47
|
+
runSmokeGoldenFlowEvidenceStep(workspace);
|
|
44
48
|
|
|
45
49
|
const lifecycleStatusSnapshot = captureLifecycleStatusSnapshot(workspace);
|
|
46
50
|
|
|
@@ -1,7 +1,15 @@
|
|
|
1
|
+
import { writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
1
3
|
import type { SmokeExpectation } from './package-install-smoke-contract';
|
|
4
|
+
import {
|
|
5
|
+
assertNoFatalOutput,
|
|
6
|
+
assertSuccess,
|
|
7
|
+
runCommand,
|
|
8
|
+
} from './package-install-smoke-runner-common';
|
|
2
9
|
import { runGateStep, type SmokeGateStep } from './package-install-smoke-gate-lib';
|
|
3
10
|
import { resolveConsumerPumukiCommand } from './package-install-smoke-command-resolution-lib';
|
|
4
11
|
import type { SmokeWorkspace } from './package-install-smoke-workspace-contract';
|
|
12
|
+
import { pushCommandLog } from './package-install-smoke-workspace-lib';
|
|
5
13
|
|
|
6
14
|
export type SmokeStepResult = {
|
|
7
15
|
label: SmokeGateStep['label'];
|
|
@@ -62,3 +70,54 @@ export const runDefaultSmokeGateSteps = (params: {
|
|
|
62
70
|
exitCode: result.exitCode,
|
|
63
71
|
};
|
|
64
72
|
});
|
|
73
|
+
|
|
74
|
+
export const runSmokeGoldenFlowEvidenceStep = (workspace: SmokeWorkspace): void => {
|
|
75
|
+
writeFileSync(
|
|
76
|
+
join(workspace.consumerRepo, 'package-smoke-minimal.feature'),
|
|
77
|
+
[
|
|
78
|
+
'Feature: Package smoke minimal',
|
|
79
|
+
'',
|
|
80
|
+
' Scenario: package smoke minimal',
|
|
81
|
+
' Given a minimal package consumer',
|
|
82
|
+
' When Pumuki gates run',
|
|
83
|
+
' Then the package smoke passes',
|
|
84
|
+
'',
|
|
85
|
+
].join('\n'),
|
|
86
|
+
'utf8'
|
|
87
|
+
);
|
|
88
|
+
const addFeatureResult = runCommand({
|
|
89
|
+
cwd: workspace.consumerRepo,
|
|
90
|
+
executable: 'git',
|
|
91
|
+
args: ['add', 'package-smoke-minimal.feature'],
|
|
92
|
+
});
|
|
93
|
+
pushCommandLog(workspace.commandLog, addFeatureResult);
|
|
94
|
+
assertSuccess(addFeatureResult, 'git add package smoke feature');
|
|
95
|
+
|
|
96
|
+
const resolvedCommand = resolveConsumerPumukiCommand({
|
|
97
|
+
consumerRepo: workspace.consumerRepo,
|
|
98
|
+
binary: 'pumuki',
|
|
99
|
+
args: [
|
|
100
|
+
'sdd',
|
|
101
|
+
'evidence',
|
|
102
|
+
'--scenario-id=package-smoke-minimal',
|
|
103
|
+
'--test-command=package-smoke-minimal',
|
|
104
|
+
'--test-status=passed',
|
|
105
|
+
'--json',
|
|
106
|
+
],
|
|
107
|
+
});
|
|
108
|
+
const result = runCommand({
|
|
109
|
+
cwd: workspace.consumerRepo,
|
|
110
|
+
executable: resolvedCommand.executable,
|
|
111
|
+
args: resolvedCommand.args,
|
|
112
|
+
env: {
|
|
113
|
+
PUMUKI_DISABLE_SYSTEM_NOTIFICATIONS: '1',
|
|
114
|
+
PUMUKI_SYSTEM_NOTIFICATIONS: '0',
|
|
115
|
+
PUMUKI_NOTIFICATIONS: '0',
|
|
116
|
+
PUMUKI_SDD_BYPASS: '1',
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
pushCommandLog(workspace.commandLog, result);
|
|
121
|
+
assertNoFatalOutput(result, 'pumuki-sdd-evidence');
|
|
122
|
+
assertSuccess(result, 'pumuki-sdd-evidence');
|
|
123
|
+
};
|