pumuki 6.3.267 → 6.3.268
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/core/gate/evaluateGate.test.ts +13 -5
- package/core/gate/evaluateGate.ts +4 -4
- package/integrations/git/astIntelligenceDualValidation.ts +1 -0
- package/integrations/git/runPlatformGate.ts +26 -4
- package/integrations/git/stageRunners.ts +4 -10
- package/integrations/lifecycle/cli.ts +14 -4
- package/integrations/lifecycle/doctor.ts +14 -0
- package/integrations/lifecycle/install.ts +12 -4
- package/integrations/lifecycle/preWriteAutomation.ts +35 -5
- package/integrations/lifecycle/preWriteLease.ts +271 -0
- package/integrations/lifecycle/status.ts +20 -2
- package/integrations/policy/skillsEnforcement.ts +2 -2
- package/integrations/policy/tddBddEnforcement.ts +3 -2
- package/package.json +1 -1
- package/scripts/package-install-smoke-execution-lib.ts +3 -1
- package/scripts/package-install-smoke-gate-lib.ts +3 -0
- package/scripts/package-install-smoke-lifecycle-lib.ts +29 -0
|
@@ -25,12 +25,14 @@ test('evaluateGate devuelve WARN cuando hay warnings sin bloqueantes', () => {
|
|
|
25
25
|
severity: 'WARN',
|
|
26
26
|
code: 'RULE_WARN',
|
|
27
27
|
message: 'Warn finding',
|
|
28
|
+
blocking: false,
|
|
28
29
|
},
|
|
29
30
|
{
|
|
30
31
|
ruleId: 'rule.info',
|
|
31
32
|
severity: 'INFO',
|
|
32
33
|
code: 'RULE_INFO',
|
|
33
34
|
message: 'Info finding',
|
|
35
|
+
blocking: false,
|
|
34
36
|
},
|
|
35
37
|
];
|
|
36
38
|
|
|
@@ -42,7 +44,7 @@ test('evaluateGate devuelve WARN cuando hay warnings sin bloqueantes', () => {
|
|
|
42
44
|
assert.equal(result.warnings[0]?.ruleId, 'rule.warn');
|
|
43
45
|
});
|
|
44
46
|
|
|
45
|
-
test('evaluateGate devuelve BLOCK
|
|
47
|
+
test('evaluateGate devuelve BLOCK para cualquier finding runtime salvo blocking=false', () => {
|
|
46
48
|
const findings: Finding[] = [
|
|
47
49
|
{
|
|
48
50
|
ruleId: 'rule.error',
|
|
@@ -62,18 +64,24 @@ test('evaluateGate devuelve BLOCK cuando existe al menos un finding bloqueante',
|
|
|
62
64
|
code: 'RULE_WARN',
|
|
63
65
|
message: 'Warn finding',
|
|
64
66
|
},
|
|
67
|
+
{
|
|
68
|
+
ruleId: 'rule.info.advisory',
|
|
69
|
+
severity: 'INFO',
|
|
70
|
+
code: 'RULE_INFO_ADVISORY',
|
|
71
|
+
message: 'Info advisory finding',
|
|
72
|
+
blocking: false,
|
|
73
|
+
},
|
|
65
74
|
];
|
|
66
75
|
|
|
67
76
|
const result = evaluateGate(findings, defaultPolicy);
|
|
68
77
|
|
|
69
78
|
assert.equal(result.outcome, 'BLOCK');
|
|
70
|
-
assert.equal(result.blocking.length,
|
|
79
|
+
assert.equal(result.blocking.length, 3);
|
|
71
80
|
assert.deepEqual(
|
|
72
81
|
result.blocking.map((finding) => finding.ruleId),
|
|
73
|
-
['rule.error', 'rule.critical']
|
|
82
|
+
['rule.error', 'rule.critical', 'rule.warn']
|
|
74
83
|
);
|
|
75
|
-
assert.
|
|
76
|
-
assert.equal(result.warnings[0]?.ruleId, 'rule.warn');
|
|
84
|
+
assert.deepEqual(result.warnings, []);
|
|
77
85
|
});
|
|
78
86
|
|
|
79
87
|
test('evaluateGate bloquea cualquier severidad cuando la policy zero-violations usa INFO', () => {
|
|
@@ -3,16 +3,16 @@ import type { GateOutcome } from './GateOutcome';
|
|
|
3
3
|
import type { GatePolicy } from './GatePolicy';
|
|
4
4
|
import { isSeverityAtLeast } from '../rules/Severity';
|
|
5
5
|
|
|
6
|
+
const isBlockingFinding = (finding: Finding): boolean => finding.blocking !== false;
|
|
7
|
+
|
|
6
8
|
export function evaluateGate(
|
|
7
9
|
findings: Finding[],
|
|
8
10
|
policy: GatePolicy
|
|
9
11
|
): { outcome: GateOutcome; blocking: Finding[]; warnings: Finding[] } {
|
|
10
|
-
const blocking = findings.filter(
|
|
11
|
-
isSeverityAtLeast(finding.severity, policy.blockOnOrAbove)
|
|
12
|
-
);
|
|
12
|
+
const blocking = findings.filter(isBlockingFinding);
|
|
13
13
|
const warnings = findings.filter(
|
|
14
14
|
(finding) =>
|
|
15
|
-
!
|
|
15
|
+
!isBlockingFinding(finding) &&
|
|
16
16
|
isSeverityAtLeast(finding.severity, policy.warnOnOrAbove)
|
|
17
17
|
);
|
|
18
18
|
|
|
@@ -188,6 +188,7 @@ const toDualValidationFinding = (params: {
|
|
|
188
188
|
return {
|
|
189
189
|
ruleId: 'governance.ast-intelligence.dual-validation.shadow',
|
|
190
190
|
severity: 'INFO',
|
|
191
|
+
blocking: false,
|
|
191
192
|
code: 'AST_INTELLIGENCE_DUAL_VALIDATION_SHADOW',
|
|
192
193
|
message:
|
|
193
194
|
`AST Intelligence dual validation shadow at ${params.stage}: ` +
|
|
@@ -39,6 +39,10 @@ import {
|
|
|
39
39
|
filterFactsByPathPrefixes,
|
|
40
40
|
resolveGateScopePathPrefixesFromEnv,
|
|
41
41
|
} from './filterFactsByPathPrefixes';
|
|
42
|
+
import {
|
|
43
|
+
readPreWriteLeaseStatus,
|
|
44
|
+
toPreWriteEnforcementGapFinding,
|
|
45
|
+
} from '../lifecycle/preWriteLease';
|
|
42
46
|
|
|
43
47
|
export type OperationalMemoryShadowRecommendation = {
|
|
44
48
|
recommendedOutcome: 'ALLOW' | 'WARN' | 'BLOCK';
|
|
@@ -801,7 +805,7 @@ const shouldBlockFromFinding = (finding: Finding | undefined): boolean => {
|
|
|
801
805
|
if (!finding) {
|
|
802
806
|
return false;
|
|
803
807
|
}
|
|
804
|
-
return finding.
|
|
808
|
+
return finding.blocking !== false;
|
|
805
809
|
};
|
|
806
810
|
|
|
807
811
|
const applySkillsFindingEnforcement = (
|
|
@@ -817,6 +821,7 @@ const applySkillsFindingEnforcement = (
|
|
|
817
821
|
return {
|
|
818
822
|
...finding,
|
|
819
823
|
severity: 'WARN',
|
|
824
|
+
blocking: false,
|
|
820
825
|
};
|
|
821
826
|
};
|
|
822
827
|
|
|
@@ -834,6 +839,7 @@ const toSoftPreCommitSkillsFinding = (params: {
|
|
|
834
839
|
return {
|
|
835
840
|
...params.finding,
|
|
836
841
|
severity: 'WARN',
|
|
842
|
+
blocking: false,
|
|
837
843
|
code: `${params.finding.code}_SOFT_PRECOMMIT`,
|
|
838
844
|
message:
|
|
839
845
|
`${params.finding.message} ` +
|
|
@@ -939,6 +945,18 @@ export async function runPlatformGate(params: {
|
|
|
939
945
|
: facts;
|
|
940
946
|
const filesScanned = countScannedFilesFromFacts(factsForPlatformEvaluation);
|
|
941
947
|
const observedCodePaths = collectObservedCodePathsFromFacts(facts);
|
|
948
|
+
const preWriteLeaseFinding =
|
|
949
|
+
params.policy.stage === 'PRE_COMMIT' ||
|
|
950
|
+
params.policy.stage === 'PRE_PUSH' ||
|
|
951
|
+
params.policy.stage === 'CI'
|
|
952
|
+
? toPreWriteEnforcementGapFinding({
|
|
953
|
+
stage: params.policy.stage,
|
|
954
|
+
status: readPreWriteLeaseStatus({
|
|
955
|
+
repoRoot,
|
|
956
|
+
git,
|
|
957
|
+
}),
|
|
958
|
+
})
|
|
959
|
+
: undefined;
|
|
942
960
|
|
|
943
961
|
const platformEvaluation = dependencies.evaluatePlatformGateFindings({
|
|
944
962
|
facts: factsForPlatformEvaluation,
|
|
@@ -1184,12 +1202,12 @@ export async function runPlatformGate(params: {
|
|
|
1184
1202
|
? tddBddEvaluation.snapshot
|
|
1185
1203
|
: undefined;
|
|
1186
1204
|
const hasTddBddBlockingFinding = tddBddEvaluation.findings.some(
|
|
1187
|
-
(finding) => finding
|
|
1205
|
+
(finding) => shouldBlockFromFinding(finding)
|
|
1188
1206
|
);
|
|
1189
1207
|
const hasNativeBlockingFinding = findings.some(
|
|
1190
|
-
(finding) => finding
|
|
1208
|
+
(finding) => shouldBlockFromFinding(finding)
|
|
1191
1209
|
);
|
|
1192
|
-
const preCommitSoftSkillsEnabled = process.env.PUMUKI_PRE_COMMIT_SOFT_SKILLS
|
|
1210
|
+
const preCommitSoftSkillsEnabled = process.env.PUMUKI_PRE_COMMIT_SOFT_SKILLS === '1';
|
|
1193
1211
|
const lowRiskPreCommitWindow = observedCodePaths.length > 0 && observedCodePaths.length <= 3;
|
|
1194
1212
|
const shouldSoftEnforceSkillsFindings =
|
|
1195
1213
|
params.policy.stage === 'PRE_COMMIT'
|
|
@@ -1228,6 +1246,7 @@ export async function runPlatformGate(params: {
|
|
|
1228
1246
|
? [
|
|
1229
1247
|
sddBlockingFinding,
|
|
1230
1248
|
...(degradedModeFinding ? [degradedModeFinding] : []),
|
|
1249
|
+
...(preWriteLeaseFinding ? [preWriteLeaseFinding] : []),
|
|
1231
1250
|
...(policyAsCodeBlockingFinding ? [policyAsCodeBlockingFinding] : []),
|
|
1232
1251
|
...(effectiveUnsupportedSkillsMappingFinding ? [effectiveUnsupportedSkillsMappingFinding] : []),
|
|
1233
1252
|
...(effectivePlatformSkillsCoverageFinding ? [effectivePlatformSkillsCoverageFinding] : []),
|
|
@@ -1245,6 +1264,7 @@ export async function runPlatformGate(params: {
|
|
|
1245
1264
|
|| effectivePlatformSkillsCoverageFinding
|
|
1246
1265
|
|| effectiveCrossPlatformCriticalFinding
|
|
1247
1266
|
|| effectiveSkillsScopeComplianceFinding
|
|
1267
|
+
|| preWriteLeaseFinding
|
|
1248
1268
|
|| activeRulesEmptyForCodeChangesFinding
|
|
1249
1269
|
|| effectiveIosTestsQualityFinding
|
|
1250
1270
|
|| astIntelligenceDualFinding
|
|
@@ -1255,6 +1275,7 @@ export async function runPlatformGate(params: {
|
|
|
1255
1275
|
|| tddBddEvaluation.findings.length > 0
|
|
1256
1276
|
? [
|
|
1257
1277
|
...(degradedModeFinding ? [degradedModeFinding] : []),
|
|
1278
|
+
...(preWriteLeaseFinding ? [preWriteLeaseFinding] : []),
|
|
1258
1279
|
...(policyAsCodeBlockingFinding ? [policyAsCodeBlockingFinding] : []),
|
|
1259
1280
|
...(effectiveUnsupportedSkillsMappingFinding ? [effectiveUnsupportedSkillsMappingFinding] : []),
|
|
1260
1281
|
...(effectivePlatformSkillsCoverageFinding ? [effectivePlatformSkillsCoverageFinding] : []),
|
|
@@ -1284,6 +1305,7 @@ export async function runPlatformGate(params: {
|
|
|
1284
1305
|
const baseGateOutcome =
|
|
1285
1306
|
sddBlockingFinding ||
|
|
1286
1307
|
degradedModeBlocks ||
|
|
1308
|
+
shouldBlockFromFinding(preWriteLeaseFinding) ||
|
|
1287
1309
|
shouldBlockFromFinding(policyAsCodeBlockingFinding) ||
|
|
1288
1310
|
shouldBlockFromFinding(effectiveUnsupportedSkillsMappingFinding) ||
|
|
1289
1311
|
shouldBlockFromFinding(effectivePlatformSkillsCoverageFinding) ||
|
|
@@ -818,7 +818,7 @@ export async function runPreCommitStage(
|
|
|
818
818
|
repoRoot,
|
|
819
819
|
stage: 'PRE_COMMIT',
|
|
820
820
|
scope: {
|
|
821
|
-
kind: '
|
|
821
|
+
kind: 'workingTree',
|
|
822
822
|
},
|
|
823
823
|
});
|
|
824
824
|
if (
|
|
@@ -898,9 +898,7 @@ export async function runPrePushStage(
|
|
|
898
898
|
repoRoot,
|
|
899
899
|
stage: 'PRE_PUSH',
|
|
900
900
|
scope: {
|
|
901
|
-
kind: '
|
|
902
|
-
fromRef: bootstrapBaseRef,
|
|
903
|
-
toRef: 'HEAD',
|
|
901
|
+
kind: 'repoAndStaged',
|
|
904
902
|
},
|
|
905
903
|
});
|
|
906
904
|
if (
|
|
@@ -1006,9 +1004,7 @@ export async function runPrePushStage(
|
|
|
1006
1004
|
repoRoot,
|
|
1007
1005
|
stage: 'PRE_PUSH',
|
|
1008
1006
|
scope: {
|
|
1009
|
-
kind: '
|
|
1010
|
-
fromRef: prePushFromRef,
|
|
1011
|
-
toRef: prePushToRef,
|
|
1007
|
+
kind: 'repoAndStaged',
|
|
1012
1008
|
},
|
|
1013
1009
|
sddDecisionOverride: historicalPrePushSddOverride,
|
|
1014
1010
|
});
|
|
@@ -1064,9 +1060,7 @@ export async function runCiStage(
|
|
|
1064
1060
|
policy: resolved.policy,
|
|
1065
1061
|
policyTrace: resolved.trace,
|
|
1066
1062
|
scope: {
|
|
1067
|
-
kind: '
|
|
1068
|
-
fromRef: ciBaseRef,
|
|
1069
|
-
toRef: 'HEAD',
|
|
1063
|
+
kind: 'repo',
|
|
1070
1064
|
},
|
|
1071
1065
|
});
|
|
1072
1066
|
if (exitCode !== 0) {
|
|
@@ -2849,8 +2849,18 @@ export const runLifecycleCli = async (
|
|
|
2849
2849
|
}
|
|
2850
2850
|
};
|
|
2851
2851
|
|
|
2852
|
-
if (
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2852
|
+
if (
|
|
2853
|
+
require.main === module &&
|
|
2854
|
+
process.env.PUMUKI_RUNTIME_EXECUTION_SOURCE === 'source-bin'
|
|
2855
|
+
) {
|
|
2856
|
+
void runLifecycleCli(process.argv.slice(2)).then(
|
|
2857
|
+
(code) => {
|
|
2858
|
+
process.exit(code);
|
|
2859
|
+
},
|
|
2860
|
+
(error) => {
|
|
2861
|
+
const message = error instanceof Error ? error.message : 'Unexpected lifecycle CLI error.';
|
|
2862
|
+
writeError(message);
|
|
2863
|
+
process.exit(1);
|
|
2864
|
+
}
|
|
2865
|
+
);
|
|
2856
2866
|
}
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
readLifecycleDependencyInventory,
|
|
21
21
|
type LifecycleDependencyInventory,
|
|
22
22
|
} from './dependencyInventory';
|
|
23
|
+
import { readPreWriteLeaseStatus } from './preWriteLease';
|
|
23
24
|
|
|
24
25
|
export type DoctorIssueSeverity = 'warning' | 'error';
|
|
25
26
|
|
|
@@ -117,6 +118,19 @@ const buildDoctorIssues = (params: {
|
|
|
117
118
|
}): ReadonlyArray<DoctorIssue> => {
|
|
118
119
|
const issues: DoctorIssue[] = [];
|
|
119
120
|
const evidenceResult = readEvidenceResult(params.repoRoot);
|
|
121
|
+
const preWriteLeaseStatus = readPreWriteLeaseStatus({
|
|
122
|
+
repoRoot: params.repoRoot,
|
|
123
|
+
git: new LifecycleGitService(),
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
if (!preWriteLeaseStatus.valid && preWriteLeaseStatus.changedCodePaths.length > 0) {
|
|
127
|
+
issues.push({
|
|
128
|
+
severity: 'error',
|
|
129
|
+
message:
|
|
130
|
+
`ENFORCEMENT_GAP: PRE_WRITE hard-stop lease is not valid (${preWriteLeaseStatus.code}) ` +
|
|
131
|
+
`for ${preWriteLeaseStatus.changedCodePaths.length} changed code file(s).`,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
120
134
|
|
|
121
135
|
if (params.trackedNodeModulesPaths.length > 0) {
|
|
122
136
|
issues.push({
|
|
@@ -2,7 +2,7 @@ import { existsSync } from 'node:fs';
|
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { installPumukiHooks } from './hookManager';
|
|
4
4
|
import { LifecycleGitService, type ILifecycleGitService } from './gitService';
|
|
5
|
-
import {
|
|
5
|
+
import { runLifecycleDoctor } from './doctor';
|
|
6
6
|
import { runOpenSpecBootstrap, type OpenSpecBootstrapResult } from './openSpecBootstrap';
|
|
7
7
|
import { LifecycleNpmService, type ILifecycleNpmService } from './npmService';
|
|
8
8
|
import { getCurrentPumukiVersion } from './packageInfo';
|
|
@@ -99,6 +99,9 @@ const materializeStrictPolicyAsCode = (repoRoot: string): void => {
|
|
|
99
99
|
}
|
|
100
100
|
};
|
|
101
101
|
|
|
102
|
+
const isPreWriteEnforcementGapIssue = (message: string): boolean =>
|
|
103
|
+
message.startsWith('ENFORCEMENT_GAP: PRE_WRITE hard-stop lease is not valid');
|
|
104
|
+
|
|
102
105
|
export const runLifecycleInstall = (params?: {
|
|
103
106
|
cwd?: string;
|
|
104
107
|
git?: ILifecycleGitService;
|
|
@@ -114,8 +117,13 @@ export const runLifecycleInstall = (params?: {
|
|
|
114
117
|
cwd: params?.cwd,
|
|
115
118
|
git,
|
|
116
119
|
});
|
|
120
|
+
const installBlockingIssues = report.issues.filter(
|
|
121
|
+
(issue) => issue.severity === 'error' && !isPreWriteEnforcementGapIssue(issue.message)
|
|
122
|
+
);
|
|
123
|
+
const hasInstallBlockingIssues =
|
|
124
|
+
installBlockingIssues.length > 0 || report.deep?.blocking === true;
|
|
117
125
|
|
|
118
|
-
if (
|
|
126
|
+
if (hasInstallBlockingIssues) {
|
|
119
127
|
if (bestEffortAfterDoctorBlock) {
|
|
120
128
|
const version = getCurrentPumukiVersion();
|
|
121
129
|
const priorArtifacts = readOpenSpecManagedArtifacts(git, report.repoRoot);
|
|
@@ -135,8 +143,8 @@ export const runLifecycleInstall = (params?: {
|
|
|
135
143
|
degradedDoctorBypass: true,
|
|
136
144
|
};
|
|
137
145
|
}
|
|
138
|
-
const renderedIssues =
|
|
139
|
-
const firstIssue =
|
|
146
|
+
const renderedIssues = installBlockingIssues.map((issue) => `- [${issue.severity}] ${issue.message}`).join('\n');
|
|
147
|
+
const firstIssue = installBlockingIssues[0];
|
|
140
148
|
const notificationResult = (params?.notifyGateBlocked ?? emitGateBlockedNotification)({
|
|
141
149
|
repoRoot: report.repoRoot,
|
|
142
150
|
stage: 'PRE_COMMIT',
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
1
2
|
import { evaluateAiGate } from '../gate/evaluateAiGate';
|
|
3
|
+
import { GitService } from '../git/GitService';
|
|
2
4
|
import { runPlatformGate } from '../git/runPlatformGate';
|
|
3
5
|
import { runEnterpriseAiGateCheck } from '../mcp/aiGateCheck';
|
|
4
6
|
import { writeMcpAiGateReceipt } from '../mcp/aiGateReceipt';
|
|
5
7
|
import { evaluateSddPolicy } from '../sdd';
|
|
8
|
+
import { writePreWriteLease } from './preWriteLease';
|
|
6
9
|
|
|
7
10
|
const PRE_WRITE_AUTOMATION_RETRY_BACKOFF_MS = 200;
|
|
8
11
|
|
|
@@ -33,7 +36,7 @@ const PRE_WRITE_AUTOFIXABLE_MCP_RECEIPT_CODES = new Set<string>([
|
|
|
33
36
|
]);
|
|
34
37
|
|
|
35
38
|
export type PreWriteAutomationAction = {
|
|
36
|
-
action: 'refresh_evidence' | 'refresh_mcp_receipt' | 'retry_backoff';
|
|
39
|
+
action: 'refresh_evidence' | 'refresh_mcp_receipt' | 'retry_backoff' | 'write_prewrite_lease';
|
|
37
40
|
status: 'OK' | 'FAILED';
|
|
38
41
|
details: string;
|
|
39
42
|
};
|
|
@@ -47,6 +50,7 @@ type PreWriteAutomationDependencies = {
|
|
|
47
50
|
evaluateAiGate: typeof evaluateAiGate;
|
|
48
51
|
runEnterpriseAiGateCheck: typeof runEnterpriseAiGateCheck;
|
|
49
52
|
writeMcpAiGateReceipt: typeof writeMcpAiGateReceipt;
|
|
53
|
+
writePreWriteLease: typeof writePreWriteLease;
|
|
50
54
|
sleep: (ms: number) => Promise<void>;
|
|
51
55
|
retryBackoffMs: number;
|
|
52
56
|
};
|
|
@@ -55,6 +59,7 @@ const defaultDependencies: PreWriteAutomationDependencies = {
|
|
|
55
59
|
evaluateAiGate,
|
|
56
60
|
runEnterpriseAiGateCheck,
|
|
57
61
|
writeMcpAiGateReceipt,
|
|
62
|
+
writePreWriteLease,
|
|
58
63
|
sleep: (ms: number) =>
|
|
59
64
|
new Promise((resolve) => {
|
|
60
65
|
setTimeout(resolve, ms);
|
|
@@ -98,7 +103,7 @@ export const buildPreWriteAutomationTrace = async (params: {
|
|
|
98
103
|
attempted: false,
|
|
99
104
|
actions: [],
|
|
100
105
|
};
|
|
101
|
-
if (params.sdd.stage !== 'PRE_WRITE'
|
|
106
|
+
if (params.sdd.stage !== 'PRE_WRITE') {
|
|
102
107
|
return {
|
|
103
108
|
aiGate: params.aiGate,
|
|
104
109
|
trace,
|
|
@@ -106,14 +111,17 @@ export const buildPreWriteAutomationTrace = async (params: {
|
|
|
106
111
|
}
|
|
107
112
|
|
|
108
113
|
let aiGate = params.aiGate;
|
|
109
|
-
if (
|
|
114
|
+
if (
|
|
115
|
+
process.env.PUMUKI_PRE_WRITE_REFRESH_GATE !== '0' ||
|
|
116
|
+
hasAutoFixableEvidenceViolation(aiGate)
|
|
117
|
+
) {
|
|
110
118
|
trace.attempted = true;
|
|
111
119
|
try {
|
|
112
120
|
const gateExitCode = await params.runPlatformGate({
|
|
113
121
|
policy: {
|
|
114
122
|
stage: 'PRE_COMMIT',
|
|
115
|
-
blockOnOrAbove: '
|
|
116
|
-
warnOnOrAbove: '
|
|
123
|
+
blockOnOrAbove: 'INFO',
|
|
124
|
+
warnOnOrAbove: 'INFO',
|
|
117
125
|
},
|
|
118
126
|
scope: {
|
|
119
127
|
kind: 'workingTree',
|
|
@@ -235,6 +243,28 @@ export const buildPreWriteAutomationTrace = async (params: {
|
|
|
235
243
|
}
|
|
236
244
|
}
|
|
237
245
|
|
|
246
|
+
if (params.sdd.decision.allowed && aiGate.allowed && existsSync(params.repoRoot)) {
|
|
247
|
+
trace.attempted = true;
|
|
248
|
+
try {
|
|
249
|
+
const git = new GitService();
|
|
250
|
+
const leaseResult = activeDependencies.writePreWriteLease({
|
|
251
|
+
repoRoot: params.repoRoot,
|
|
252
|
+
git,
|
|
253
|
+
});
|
|
254
|
+
trace.actions.push({
|
|
255
|
+
action: 'write_prewrite_lease',
|
|
256
|
+
status: leaseResult.valid ? 'OK' : 'FAILED',
|
|
257
|
+
details: `${leaseResult.code} path=${leaseResult.path}`,
|
|
258
|
+
});
|
|
259
|
+
} catch (error) {
|
|
260
|
+
trace.actions.push({
|
|
261
|
+
action: 'write_prewrite_lease',
|
|
262
|
+
status: 'FAILED',
|
|
263
|
+
details: error instanceof Error ? error.message : 'Unknown PRE_WRITE lease write error',
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
238
268
|
return {
|
|
239
269
|
aiGate,
|
|
240
270
|
trace,
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import type { Finding } from '../../core/gate/Finding';
|
|
4
|
+
import type { IGitService } from '../git/GitService';
|
|
5
|
+
import { hasAllowedExtension } from '../git/gitDiffUtils';
|
|
6
|
+
import { DEFAULT_FACT_FILE_EXTENSIONS } from '../git/runPlatformGateFacts';
|
|
7
|
+
|
|
8
|
+
export type PreWriteLease = {
|
|
9
|
+
version: '1';
|
|
10
|
+
kind: 'pumuki-pre-write-lease';
|
|
11
|
+
repo_root: string;
|
|
12
|
+
head: string;
|
|
13
|
+
branch: string | null;
|
|
14
|
+
issued_at: string;
|
|
15
|
+
expires_at: string;
|
|
16
|
+
pre_change_code_changes_count: number;
|
|
17
|
+
pre_change_code_paths: string[];
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type PreWriteLeaseStatus =
|
|
21
|
+
| {
|
|
22
|
+
valid: true;
|
|
23
|
+
code: 'PRE_WRITE_LEASE_VALID';
|
|
24
|
+
path: string;
|
|
25
|
+
lease: PreWriteLease;
|
|
26
|
+
changedCodePaths: string[];
|
|
27
|
+
}
|
|
28
|
+
| {
|
|
29
|
+
valid: false;
|
|
30
|
+
code:
|
|
31
|
+
| 'PRE_WRITE_LEASE_MISSING'
|
|
32
|
+
| 'PRE_WRITE_LEASE_INVALID'
|
|
33
|
+
| 'PRE_WRITE_LEASE_EXPIRED'
|
|
34
|
+
| 'PRE_WRITE_LEASE_HEAD_MISMATCH'
|
|
35
|
+
| 'PRE_WRITE_LEASE_DIRTY_AT_ISSUE';
|
|
36
|
+
path: string;
|
|
37
|
+
message: string;
|
|
38
|
+
changedCodePaths: string[];
|
|
39
|
+
lease?: PreWriteLease;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type PreWriteLeaseWriteResult = {
|
|
43
|
+
path: string;
|
|
44
|
+
written: boolean;
|
|
45
|
+
valid: boolean;
|
|
46
|
+
code: 'PRE_WRITE_LEASE_WRITTEN' | 'PRE_WRITE_LEASE_NOT_WRITTEN_DIRTY_CODE';
|
|
47
|
+
message: string;
|
|
48
|
+
lease?: PreWriteLease;
|
|
49
|
+
changedCodePaths: string[];
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const PRE_WRITE_LEASE_TTL_MS = 4 * 60 * 60 * 1000;
|
|
53
|
+
|
|
54
|
+
export const resolvePreWriteLeasePath = (repoRoot: string): string =>
|
|
55
|
+
join(repoRoot, '.pumuki', 'prewrite-lease.json');
|
|
56
|
+
|
|
57
|
+
const runGitOrEmpty = (
|
|
58
|
+
git: Pick<IGitService, 'runGit'>,
|
|
59
|
+
repoRoot: string,
|
|
60
|
+
args: ReadonlyArray<string>
|
|
61
|
+
): string => {
|
|
62
|
+
try {
|
|
63
|
+
return git.runGit([...args], repoRoot);
|
|
64
|
+
} catch {
|
|
65
|
+
return '';
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const collectCodePathsFromOutput = (output: string): string[] =>
|
|
70
|
+
output
|
|
71
|
+
.split('\n')
|
|
72
|
+
.map((line) => line.trim())
|
|
73
|
+
.filter((line) => line.length > 0)
|
|
74
|
+
.filter((line) => hasAllowedExtension(line, DEFAULT_FACT_FILE_EXTENSIONS));
|
|
75
|
+
|
|
76
|
+
export const collectPreWriteCodeChangePaths = (params: {
|
|
77
|
+
repoRoot: string;
|
|
78
|
+
git: Pick<IGitService, 'runGit'>;
|
|
79
|
+
}): string[] => {
|
|
80
|
+
const paths = new Set<string>();
|
|
81
|
+
for (const output of [
|
|
82
|
+
runGitOrEmpty(params.git, params.repoRoot, ['diff', '--name-only']),
|
|
83
|
+
runGitOrEmpty(params.git, params.repoRoot, ['diff', '--cached', '--name-only']),
|
|
84
|
+
runGitOrEmpty(params.git, params.repoRoot, ['ls-files', '--others', '--exclude-standard']),
|
|
85
|
+
]) {
|
|
86
|
+
for (const path of collectCodePathsFromOutput(output)) {
|
|
87
|
+
paths.add(path);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return [...paths].sort((left, right) => left.localeCompare(right));
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const resolveHead = (params: {
|
|
94
|
+
repoRoot: string;
|
|
95
|
+
git: Pick<IGitService, 'runGit'>;
|
|
96
|
+
}): string => runGitOrEmpty(params.git, params.repoRoot, ['rev-parse', 'HEAD']).trim();
|
|
97
|
+
|
|
98
|
+
const resolveBranch = (params: {
|
|
99
|
+
repoRoot: string;
|
|
100
|
+
git: Pick<IGitService, 'runGit'>;
|
|
101
|
+
}): string | null => {
|
|
102
|
+
const branch = runGitOrEmpty(params.git, params.repoRoot, ['rev-parse', '--abbrev-ref', 'HEAD']).trim();
|
|
103
|
+
return branch.length === 0 || branch === 'HEAD' ? null : branch;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const parseLease = (raw: string): PreWriteLease | undefined => {
|
|
107
|
+
try {
|
|
108
|
+
const value = JSON.parse(raw) as Partial<PreWriteLease>;
|
|
109
|
+
if (
|
|
110
|
+
value.version === '1' &&
|
|
111
|
+
value.kind === 'pumuki-pre-write-lease' &&
|
|
112
|
+
typeof value.repo_root === 'string' &&
|
|
113
|
+
typeof value.head === 'string' &&
|
|
114
|
+
typeof value.issued_at === 'string' &&
|
|
115
|
+
typeof value.expires_at === 'string' &&
|
|
116
|
+
typeof value.pre_change_code_changes_count === 'number' &&
|
|
117
|
+
Array.isArray(value.pre_change_code_paths)
|
|
118
|
+
) {
|
|
119
|
+
return {
|
|
120
|
+
version: '1',
|
|
121
|
+
kind: 'pumuki-pre-write-lease',
|
|
122
|
+
repo_root: value.repo_root,
|
|
123
|
+
head: value.head,
|
|
124
|
+
branch: typeof value.branch === 'string' ? value.branch : null,
|
|
125
|
+
issued_at: value.issued_at,
|
|
126
|
+
expires_at: value.expires_at,
|
|
127
|
+
pre_change_code_changes_count: value.pre_change_code_changes_count,
|
|
128
|
+
pre_change_code_paths: value.pre_change_code_paths.filter(
|
|
129
|
+
(item): item is string => typeof item === 'string'
|
|
130
|
+
),
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
} catch {
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
return undefined;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
export const readPreWriteLeaseStatus = (params: {
|
|
140
|
+
repoRoot: string;
|
|
141
|
+
git: Pick<IGitService, 'runGit'>;
|
|
142
|
+
now?: Date;
|
|
143
|
+
}): PreWriteLeaseStatus => {
|
|
144
|
+
const path = resolvePreWriteLeasePath(params.repoRoot);
|
|
145
|
+
const changedCodePaths = collectPreWriteCodeChangePaths(params);
|
|
146
|
+
if (!existsSync(path)) {
|
|
147
|
+
return {
|
|
148
|
+
valid: false,
|
|
149
|
+
code: 'PRE_WRITE_LEASE_MISSING',
|
|
150
|
+
path,
|
|
151
|
+
changedCodePaths,
|
|
152
|
+
message: 'No valid PRE_WRITE lease exists for this code diff.',
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const lease = parseLease(readFileSync(path, 'utf8'));
|
|
157
|
+
if (!lease) {
|
|
158
|
+
return {
|
|
159
|
+
valid: false,
|
|
160
|
+
code: 'PRE_WRITE_LEASE_INVALID',
|
|
161
|
+
path,
|
|
162
|
+
changedCodePaths,
|
|
163
|
+
message: 'PRE_WRITE lease is invalid or unreadable.',
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (lease.head !== resolveHead(params)) {
|
|
168
|
+
return {
|
|
169
|
+
valid: false,
|
|
170
|
+
code: 'PRE_WRITE_LEASE_HEAD_MISMATCH',
|
|
171
|
+
path,
|
|
172
|
+
lease,
|
|
173
|
+
changedCodePaths,
|
|
174
|
+
message: 'PRE_WRITE lease was issued for a different HEAD.',
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (Date.parse(lease.expires_at) <= (params.now ?? new Date()).getTime()) {
|
|
179
|
+
return {
|
|
180
|
+
valid: false,
|
|
181
|
+
code: 'PRE_WRITE_LEASE_EXPIRED',
|
|
182
|
+
path,
|
|
183
|
+
lease,
|
|
184
|
+
changedCodePaths,
|
|
185
|
+
message: 'PRE_WRITE lease has expired.',
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (lease.pre_change_code_changes_count !== 0 || lease.pre_change_code_paths.length !== 0) {
|
|
190
|
+
return {
|
|
191
|
+
valid: false,
|
|
192
|
+
code: 'PRE_WRITE_LEASE_DIRTY_AT_ISSUE',
|
|
193
|
+
path,
|
|
194
|
+
lease,
|
|
195
|
+
changedCodePaths,
|
|
196
|
+
message: 'PRE_WRITE lease was issued after code changes already existed.',
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
valid: true,
|
|
202
|
+
code: 'PRE_WRITE_LEASE_VALID',
|
|
203
|
+
path,
|
|
204
|
+
lease,
|
|
205
|
+
changedCodePaths,
|
|
206
|
+
};
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
export const writePreWriteLease = (params: {
|
|
210
|
+
repoRoot: string;
|
|
211
|
+
git: Pick<IGitService, 'runGit'>;
|
|
212
|
+
now?: Date;
|
|
213
|
+
}): PreWriteLeaseWriteResult => {
|
|
214
|
+
const path = resolvePreWriteLeasePath(params.repoRoot);
|
|
215
|
+
const changedCodePaths = collectPreWriteCodeChangePaths(params);
|
|
216
|
+
if (changedCodePaths.length > 0) {
|
|
217
|
+
return {
|
|
218
|
+
path,
|
|
219
|
+
written: false,
|
|
220
|
+
valid: false,
|
|
221
|
+
code: 'PRE_WRITE_LEASE_NOT_WRITTEN_DIRTY_CODE',
|
|
222
|
+
changedCodePaths,
|
|
223
|
+
message:
|
|
224
|
+
`PRE_WRITE lease not written because code changes already exist: ${changedCodePaths.join(', ')}`,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const issuedAt = params.now ?? new Date();
|
|
229
|
+
const lease: PreWriteLease = {
|
|
230
|
+
version: '1',
|
|
231
|
+
kind: 'pumuki-pre-write-lease',
|
|
232
|
+
repo_root: params.repoRoot,
|
|
233
|
+
head: resolveHead(params),
|
|
234
|
+
branch: resolveBranch(params),
|
|
235
|
+
issued_at: issuedAt.toISOString(),
|
|
236
|
+
expires_at: new Date(issuedAt.getTime() + PRE_WRITE_LEASE_TTL_MS).toISOString(),
|
|
237
|
+
pre_change_code_changes_count: 0,
|
|
238
|
+
pre_change_code_paths: [],
|
|
239
|
+
};
|
|
240
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
241
|
+
writeFileSync(path, `${JSON.stringify(lease, null, 2)}\n`, 'utf8');
|
|
242
|
+
return {
|
|
243
|
+
path,
|
|
244
|
+
written: true,
|
|
245
|
+
valid: true,
|
|
246
|
+
code: 'PRE_WRITE_LEASE_WRITTEN',
|
|
247
|
+
changedCodePaths,
|
|
248
|
+
message: 'PRE_WRITE lease written for clean code state.',
|
|
249
|
+
lease,
|
|
250
|
+
};
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
export const toPreWriteEnforcementGapFinding = (params: {
|
|
254
|
+
stage: 'PRE_COMMIT' | 'PRE_PUSH' | 'CI';
|
|
255
|
+
status: PreWriteLeaseStatus;
|
|
256
|
+
}): Finding | undefined => {
|
|
257
|
+
if (params.status.valid || params.status.changedCodePaths.length === 0) {
|
|
258
|
+
return undefined;
|
|
259
|
+
}
|
|
260
|
+
return {
|
|
261
|
+
ruleId: 'governance.prewrite.enforcement-gap',
|
|
262
|
+
severity: 'ERROR',
|
|
263
|
+
code: 'ENFORCEMENT_GAP_PRE_WRITE_LEASE_MISSING',
|
|
264
|
+
message:
|
|
265
|
+
`PRE_WRITE hard-stop lease is not valid at ${params.stage}: ${params.status.code}. ` +
|
|
266
|
+
'Run PRE_WRITE before editing code; hooks/audit cannot accept code diffs without a prior clean PRE_WRITE lease.',
|
|
267
|
+
filePath: '.pumuki/prewrite-lease.json',
|
|
268
|
+
matchedBy: 'PreWriteLeaseGuard',
|
|
269
|
+
source: 'prewrite-lease',
|
|
270
|
+
};
|
|
271
|
+
};
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
readLifecycleDependencyInventory,
|
|
18
18
|
type LifecycleDependencyInventory,
|
|
19
19
|
} from './dependencyInventory';
|
|
20
|
+
import { readPreWriteLeaseStatus } from './preWriteLease';
|
|
20
21
|
|
|
21
22
|
export type LifecycleStatus = {
|
|
22
23
|
repoRoot: string;
|
|
@@ -35,8 +36,23 @@ export type LifecycleStatus = {
|
|
|
35
36
|
|
|
36
37
|
const buildLifecycleIssues = (repoRoot: string): ReadonlyArray<DoctorIssue> => {
|
|
37
38
|
const evidenceResult = readEvidenceResult(repoRoot);
|
|
39
|
+
const preWriteLeaseStatus = readPreWriteLeaseStatus({
|
|
40
|
+
repoRoot,
|
|
41
|
+
git: new LifecycleGitService(),
|
|
42
|
+
});
|
|
43
|
+
const leaseIssues: DoctorIssue[] =
|
|
44
|
+
!preWriteLeaseStatus.valid && preWriteLeaseStatus.changedCodePaths.length > 0
|
|
45
|
+
? [
|
|
46
|
+
{
|
|
47
|
+
severity: 'error',
|
|
48
|
+
message:
|
|
49
|
+
`ENFORCEMENT_GAP: PRE_WRITE hard-stop lease is not valid (${preWriteLeaseStatus.code}) ` +
|
|
50
|
+
`for ${preWriteLeaseStatus.changedCodePaths.length} changed code file(s).`,
|
|
51
|
+
},
|
|
52
|
+
]
|
|
53
|
+
: [];
|
|
38
54
|
if (evidenceResult.kind !== 'valid') {
|
|
39
|
-
return
|
|
55
|
+
return leaseIssues;
|
|
40
56
|
}
|
|
41
57
|
|
|
42
58
|
const evidence = evidenceResult.evidence;
|
|
@@ -47,11 +63,12 @@ const buildLifecycleIssues = (repoRoot: string): ReadonlyArray<DoctorIssue> => {
|
|
|
47
63
|
|
|
48
64
|
if (!blocked) {
|
|
49
65
|
if (evidence.snapshot.outcome !== 'WARN') {
|
|
50
|
-
return
|
|
66
|
+
return leaseIssues;
|
|
51
67
|
}
|
|
52
68
|
|
|
53
69
|
const warnStage = evidence?.snapshot?.stage ?? 'PRE_WRITE';
|
|
54
70
|
return [
|
|
71
|
+
...leaseIssues,
|
|
55
72
|
{
|
|
56
73
|
severity: 'warning',
|
|
57
74
|
message: appendTrackingActionableContext({
|
|
@@ -68,6 +85,7 @@ const buildLifecycleIssues = (repoRoot: string): ReadonlyArray<DoctorIssue> => {
|
|
|
68
85
|
message: `Governance is blocked (${blockedStage}).`,
|
|
69
86
|
});
|
|
70
87
|
return [
|
|
88
|
+
...leaseIssues,
|
|
71
89
|
{
|
|
72
90
|
severity: 'error',
|
|
73
91
|
message,
|
|
@@ -52,9 +52,9 @@ export const resolveTddBddEnforcement = (): TddBddEnforcementResolution => {
|
|
|
52
52
|
};
|
|
53
53
|
}
|
|
54
54
|
return {
|
|
55
|
-
mode: '
|
|
55
|
+
mode: 'strict',
|
|
56
56
|
source: 'default',
|
|
57
|
-
blocking:
|
|
57
|
+
blocking: true,
|
|
58
58
|
};
|
|
59
59
|
};
|
|
60
60
|
|
|
@@ -71,6 +71,7 @@ const applyTddBddFindingEnforcement = (
|
|
|
71
71
|
return {
|
|
72
72
|
...finding,
|
|
73
73
|
severity: 'WARN',
|
|
74
|
+
blocking: false,
|
|
74
75
|
};
|
|
75
76
|
};
|
|
76
77
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pumuki",
|
|
3
|
-
"version": "6.3.
|
|
3
|
+
"version": "6.3.268",
|
|
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": {
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
assertLifecycleStatusMatchesSnapshot,
|
|
5
5
|
captureLifecycleStatusSnapshot,
|
|
6
6
|
runLifecycleInstallStep,
|
|
7
|
+
runLifecyclePreWriteStep,
|
|
7
8
|
runLifecycleUninstallStep,
|
|
8
9
|
} from './package-install-smoke-lifecycle-lib';
|
|
9
10
|
import {
|
|
@@ -37,10 +38,11 @@ export const runPackageInstallSmoke = (mode: SmokeMode): void => {
|
|
|
37
38
|
});
|
|
38
39
|
|
|
39
40
|
setupConsumerRepository(workspace, mode);
|
|
41
|
+
runLifecycleInstallStep(workspace);
|
|
42
|
+
runLifecyclePreWriteStep(workspace);
|
|
40
43
|
writeStagedPayload(workspace, mode);
|
|
41
44
|
|
|
42
45
|
const lifecycleStatusSnapshot = captureLifecycleStatusSnapshot(workspace);
|
|
43
|
-
runLifecycleInstallStep(workspace);
|
|
44
46
|
|
|
45
47
|
const results = runDefaultSmokeGateSteps({
|
|
46
48
|
workspace,
|
|
@@ -25,6 +25,9 @@ export const runGateStep = (
|
|
|
25
25
|
expectation: SmokeExpectation
|
|
26
26
|
): { outcome: string; exitCode: number } => {
|
|
27
27
|
const gateEnv: NodeJS.ProcessEnv = {
|
|
28
|
+
PUMUKI_DISABLE_SYSTEM_NOTIFICATIONS: '1',
|
|
29
|
+
PUMUKI_SYSTEM_NOTIFICATIONS: '0',
|
|
30
|
+
PUMUKI_NOTIFICATIONS: '0',
|
|
28
31
|
PUMUKI_SDD_BYPASS: '1',
|
|
29
32
|
...(step.label === 'ci' ? { GITHUB_BASE_REF: 'main' } : {}),
|
|
30
33
|
};
|
|
@@ -38,6 +38,9 @@ export const runLifecycleInstallStep = (workspace: SmokeWorkspace): void => {
|
|
|
38
38
|
executable: command.executable,
|
|
39
39
|
args: command.args,
|
|
40
40
|
env: {
|
|
41
|
+
PUMUKI_DISABLE_SYSTEM_NOTIFICATIONS: '1',
|
|
42
|
+
PUMUKI_SYSTEM_NOTIFICATIONS: '0',
|
|
43
|
+
PUMUKI_NOTIFICATIONS: '0',
|
|
41
44
|
PUMUKI_SKIP_OPENSPEC_BOOTSTRAP: '1',
|
|
42
45
|
},
|
|
43
46
|
});
|
|
@@ -46,6 +49,27 @@ export const runLifecycleInstallStep = (workspace: SmokeWorkspace): void => {
|
|
|
46
49
|
assertSuccess(result, 'pumuki lifecycle install');
|
|
47
50
|
};
|
|
48
51
|
|
|
52
|
+
export const runLifecyclePreWriteStep = (workspace: SmokeWorkspace): void => {
|
|
53
|
+
const command = resolveConsumerPumukiCommand({
|
|
54
|
+
consumerRepo: workspace.consumerRepo,
|
|
55
|
+
binary: 'pumuki-pre-write',
|
|
56
|
+
});
|
|
57
|
+
const result = runCommand({
|
|
58
|
+
cwd: workspace.consumerRepo,
|
|
59
|
+
executable: command.executable,
|
|
60
|
+
args: command.args,
|
|
61
|
+
env: {
|
|
62
|
+
PUMUKI_DISABLE_SYSTEM_NOTIFICATIONS: '1',
|
|
63
|
+
PUMUKI_SYSTEM_NOTIFICATIONS: '0',
|
|
64
|
+
PUMUKI_NOTIFICATIONS: '0',
|
|
65
|
+
PUMUKI_SDD_BYPASS: '1',
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
pushCommandLog(workspace.commandLog, result);
|
|
69
|
+
assertNoFatalOutput(result, 'pumuki lifecycle pre-write');
|
|
70
|
+
assertSuccess(result, 'pumuki lifecycle pre-write');
|
|
71
|
+
};
|
|
72
|
+
|
|
49
73
|
export const runLifecycleUninstallStep = (workspace: SmokeWorkspace): void => {
|
|
50
74
|
const command = resolveConsumerPumukiCommand({
|
|
51
75
|
consumerRepo: workspace.consumerRepo,
|
|
@@ -56,6 +80,11 @@ export const runLifecycleUninstallStep = (workspace: SmokeWorkspace): void => {
|
|
|
56
80
|
cwd: workspace.consumerRepo,
|
|
57
81
|
executable: command.executable,
|
|
58
82
|
args: command.args,
|
|
83
|
+
env: {
|
|
84
|
+
PUMUKI_DISABLE_SYSTEM_NOTIFICATIONS: '1',
|
|
85
|
+
PUMUKI_SYSTEM_NOTIFICATIONS: '0',
|
|
86
|
+
PUMUKI_NOTIFICATIONS: '0',
|
|
87
|
+
},
|
|
59
88
|
});
|
|
60
89
|
pushCommandLog(workspace.commandLog, result);
|
|
61
90
|
assertNoFatalOutput(result, 'pumuki lifecycle uninstall');
|