pumuki 6.3.75 → 6.3.77

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.
@@ -233,6 +233,8 @@ type PolicyAsCodeContract = {
233
233
  version: '1.0';
234
234
  source: 'default' | 'skills.policy' | 'hard-mode';
235
235
  signatures: Partial<Record<SkillsStage, string>> & Record<'PRE_COMMIT' | 'PRE_PUSH' | 'CI', string>;
236
+ strict?: Partial<Record<SkillsStage, boolean>> &
237
+ Record<'PRE_COMMIT' | 'PRE_PUSH' | 'CI', boolean>;
236
238
  expires_at?: string;
237
239
  };
238
240
 
@@ -272,6 +274,30 @@ const policyStrictModeFromEnv = (): boolean => {
272
274
  return raw === '1' || raw === 'true' || raw === 'yes' || raw === 'on';
273
275
  };
274
276
 
277
+ const isBoolean = (value: unknown): value is boolean => {
278
+ return typeof value === 'boolean';
279
+ };
280
+
281
+ const resolveContractStrictForStage = (
282
+ strictByStage: PolicyAsCodeContract['strict'],
283
+ stage: SkillsStage
284
+ ): boolean | null => {
285
+ if (!strictByStage) {
286
+ return null;
287
+ }
288
+ if (stage === 'PRE_WRITE') {
289
+ return strictByStage.PRE_WRITE ?? strictByStage.PRE_COMMIT ?? null;
290
+ }
291
+ return strictByStage[stage] ?? null;
292
+ };
293
+
294
+ const resolvePolicyStrict = (strictByContract: boolean | null): boolean => {
295
+ if (typeof strictByContract === 'boolean') {
296
+ return strictByContract;
297
+ }
298
+ return policyStrictModeFromEnv();
299
+ };
300
+
275
301
  const isPolicyAsCodeContract = (value: unknown): value is PolicyAsCodeContract => {
276
302
  if (!isObject(value)) {
277
303
  return false;
@@ -289,6 +315,16 @@ const isPolicyAsCodeContract = (value: unknown): value is PolicyAsCodeContract =
289
315
  if (!isObject(value.signatures)) {
290
316
  return false;
291
317
  }
318
+ if (
319
+ typeof value.strict !== 'undefined' &&
320
+ (!isObject(value.strict)
321
+ || (typeof value.strict.PRE_WRITE !== 'undefined' && !isBoolean(value.strict.PRE_WRITE))
322
+ || !isBoolean(value.strict.PRE_COMMIT)
323
+ || !isBoolean(value.strict.PRE_PUSH)
324
+ || !isBoolean(value.strict.CI))
325
+ ) {
326
+ return false;
327
+ }
292
328
  if (typeof value.expires_at !== 'undefined' && !isIsoDateString(value.expires_at)) {
293
329
  return false;
294
330
  }
@@ -332,7 +368,7 @@ const resolvePolicyAsCodeTraceMetadata = (params: {
332
368
  policySource: string;
333
369
  validation: NonNullable<ResolvedStagePolicy['trace']['validation']>;
334
370
  } => {
335
- const strict = policyStrictModeFromEnv();
371
+ const envStrict = policyStrictModeFromEnv();
336
372
  const computedVersion = `policy-as-code/${params.source}@${POLICY_AS_CODE_VERSION}`;
337
373
  const computedSignature = createPolicyAsCodeSignature({
338
374
  stage: params.stage,
@@ -344,7 +380,7 @@ const resolvePolicyAsCodeTraceMetadata = (params: {
344
380
  const contractPath = join(params.repoRoot, POLICY_AS_CODE_CONTRACT_PATH);
345
381
 
346
382
  if (!existsSync(contractPath)) {
347
- if (strict) {
383
+ if (envStrict) {
348
384
  return {
349
385
  version: computedVersion,
350
386
  signature: computedSignature,
@@ -354,7 +390,7 @@ const resolvePolicyAsCodeTraceMetadata = (params: {
354
390
  code: 'POLICY_AS_CODE_UNSIGNED',
355
391
  message:
356
392
  'Policy-as-code contract is missing; runtime policy metadata is unsigned.',
357
- strict,
393
+ strict: envStrict,
358
394
  },
359
395
  };
360
396
  }
@@ -367,7 +403,7 @@ const resolvePolicyAsCodeTraceMetadata = (params: {
367
403
  status: 'valid',
368
404
  code: 'POLICY_AS_CODE_VALID',
369
405
  message: 'Policy-as-code metadata generated from active runtime policy.',
370
- strict,
406
+ strict: envStrict,
371
407
  },
372
408
  };
373
409
  }
@@ -383,11 +419,13 @@ const resolvePolicyAsCodeTraceMetadata = (params: {
383
419
  status: 'invalid',
384
420
  code: 'POLICY_AS_CODE_CONTRACT_INVALID',
385
421
  message: 'Policy-as-code contract is malformed.',
386
- strict,
422
+ strict: envStrict,
387
423
  },
388
424
  };
389
425
  }
390
426
 
427
+ const strict = resolvePolicyStrict(resolveContractStrictForStage(raw.strict, params.stage));
428
+
391
429
  if (raw.source !== params.source) {
392
430
  return {
393
431
  version: `policy-as-code/${raw.source}@${raw.version}`,
@@ -465,7 +503,7 @@ const resolvePolicyAsCodeTraceMetadata = (params: {
465
503
  status: 'invalid',
466
504
  code: 'POLICY_AS_CODE_CONTRACT_INVALID',
467
505
  message: 'Policy-as-code contract cannot be parsed as JSON.',
468
- strict,
506
+ strict: envStrict,
469
507
  },
470
508
  };
471
509
  }
@@ -103,19 +103,9 @@ const toContractSource = (
103
103
  return 'default';
104
104
  };
105
105
 
106
- const tryApplyPolicyAutofix = (params: {
106
+ const writePolicyAsCodeContract = (params: {
107
107
  report: Omit<PolicyReconcileReport, 'applyRequested' | 'autofix'>;
108
108
  }): PolicyReconcileReport['autofix'] => {
109
- const actionableDrifts = params.report.drifts.filter((drift) => POLICY_AUTOFIX_DRIFT_CODES.has(drift.code));
110
- if (actionableDrifts.length === 0) {
111
- return {
112
- attempted: false,
113
- status: 'SKIPPED',
114
- actions: [],
115
- details: 'No policy-as-code drift eligible for autofix.',
116
- };
117
- }
118
-
119
109
  const preWriteStage = params.report.stages.PRE_WRITE;
120
110
  const signatures = {
121
111
  PRE_WRITE: createPolicyAsCodeSignature({
@@ -134,7 +124,7 @@ const tryApplyPolicyAutofix = (params: {
134
124
  attempted: true,
135
125
  status: 'FAILED',
136
126
  actions: [],
137
- details: 'Cannot autofix: missing computed signatures for one or more stages.',
127
+ details: 'Cannot write policy-as-code contract: missing computed signatures for one or more stages.',
138
128
  };
139
129
  }
140
130
 
@@ -142,6 +132,12 @@ const tryApplyPolicyAutofix = (params: {
142
132
  const contract = {
143
133
  version: '1.0',
144
134
  source: toContractSource(params.report.stages.PRE_WRITE.source),
135
+ strict: {
136
+ PRE_WRITE: params.report.stages.PRE_WRITE.strict,
137
+ PRE_COMMIT: params.report.stages.PRE_COMMIT.strict,
138
+ PRE_PUSH: params.report.stages.PRE_PUSH.strict,
139
+ CI: params.report.stages.CI.strict,
140
+ },
145
141
  signatures: {
146
142
  PRE_WRITE: signatures.PRE_WRITE,
147
143
  PRE_COMMIT: signatures.PRE_COMMIT,
@@ -158,7 +154,7 @@ const tryApplyPolicyAutofix = (params: {
158
154
  attempted: true,
159
155
  status: 'APPLIED',
160
156
  actions: ['WRITE_POLICY_AS_CODE_CONTRACT'],
161
- details: `Wrote ${POLICY_AS_CODE_CONTRACT_PATH} with deterministic stage signatures.`,
157
+ details: `Wrote ${POLICY_AS_CODE_CONTRACT_PATH} with deterministic stage signatures and strict flags.`,
162
158
  };
163
159
  } catch (error) {
164
160
  return {
@@ -170,6 +166,22 @@ const tryApplyPolicyAutofix = (params: {
170
166
  }
171
167
  };
172
168
 
169
+ const tryApplyPolicyAutofix = (params: {
170
+ report: Omit<PolicyReconcileReport, 'applyRequested' | 'autofix'>;
171
+ }): PolicyReconcileReport['autofix'] => {
172
+ const actionableDrifts = params.report.drifts.filter((drift) => POLICY_AUTOFIX_DRIFT_CODES.has(drift.code));
173
+ if (actionableDrifts.length === 0) {
174
+ return {
175
+ attempted: false,
176
+ status: 'SKIPPED',
177
+ actions: [],
178
+ details: 'No policy-as-code drift eligible for autofix.',
179
+ };
180
+ }
181
+
182
+ return writePolicyAsCodeContract(params);
183
+ };
184
+
173
185
  export const runPolicyReconcile = (params?: {
174
186
  repoRoot?: string;
175
187
  now?: () => Date;
@@ -412,6 +424,31 @@ export const runPolicyReconcile = (params?: {
412
424
  };
413
425
  }
414
426
 
427
+ if (strictRequested && baseReport.summary.status === 'PASS') {
428
+ const strictPersistence = writePolicyAsCodeContract({
429
+ report: baseReport,
430
+ });
431
+ if (strictPersistence.status !== 'APPLIED') {
432
+ return {
433
+ ...baseReport,
434
+ applyRequested: true,
435
+ autofix: strictPersistence,
436
+ };
437
+ }
438
+
439
+ const reevaluated = runPolicyReconcile({
440
+ repoRoot,
441
+ now,
442
+ strict: strictRequested,
443
+ apply: false,
444
+ });
445
+ return {
446
+ ...reevaluated,
447
+ applyRequested: true,
448
+ autofix: strictPersistence,
449
+ };
450
+ }
451
+
415
452
  const autofix = tryApplyPolicyAutofix({
416
453
  report: baseReport,
417
454
  });
@@ -39,6 +39,14 @@ type EnterpriseStatusPayload = {
39
39
  evidence: ReturnType<typeof toStatusPayload>;
40
40
  };
41
41
 
42
+ type EnterpriseHealthPayload = {
43
+ status: 'ok';
44
+ repoRoot: string;
45
+ experimentalFeatures: {
46
+ mcp_enterprise: ReturnType<typeof resolveMcpEnterpriseExperimentalFeature>;
47
+ };
48
+ };
49
+
42
50
  const ENTERPRISE_RESOURCES = [
43
51
  'evidence://status',
44
52
  'gitflow://state',
@@ -650,6 +658,14 @@ const buildStatusPayload = (repoRoot: string): EnterpriseStatusPayload => ({
650
658
  evidence: toStatusPayload(repoRoot),
651
659
  });
652
660
 
661
+ const buildHealthPayload = (repoRoot: string): EnterpriseHealthPayload => ({
662
+ status: 'ok',
663
+ repoRoot,
664
+ experimentalFeatures: {
665
+ mcp_enterprise: readMcpEnterpriseExperimentalState(),
666
+ },
667
+ });
668
+
653
669
  export const startEnterpriseMcpServer = (
654
670
  options: EnterpriseServerOptions = {}
655
671
  ): EnterpriseServerHandle => {
@@ -680,7 +696,7 @@ export const startEnterpriseMcpServer = (
680
696
  sendJson(res, 405, { error: 'Method not allowed' });
681
697
  return;
682
698
  }
683
- sendJson(res, 200, { status: 'ok' });
699
+ sendJson(res, 200, buildHealthPayload(repoRoot));
684
700
  return;
685
701
  }
686
702
 
@@ -1,5 +1,6 @@
1
1
  import { Socket, createServer } from 'node:net';
2
2
  import { startEnterpriseMcpServer } from './enterpriseServer';
3
+ import { resolveMcpEnterpriseExperimentalFeature } from '../policy/experimentalFeatures';
3
4
 
4
5
  type JsonRpcId = string | number | null;
5
6
 
@@ -98,11 +99,32 @@ const fetchJson = async (url: string, options?: RequestInit): Promise<unknown> =
98
99
  }
99
100
  };
100
101
 
102
+ type EnterpriseHealthPayload = {
103
+ status?: string;
104
+ repoRoot?: string;
105
+ experimentalFeatures?: {
106
+ mcp_enterprise?: {
107
+ mode?: string;
108
+ };
109
+ };
110
+ };
111
+
112
+ const canReuseEnterpriseHttp = (
113
+ health: EnterpriseHealthPayload,
114
+ repoRoot: string,
115
+ expectedMcpMode: string
116
+ ): boolean =>
117
+ health.status === 'ok'
118
+ && health.repoRoot === repoRoot
119
+ && health.experimentalFeatures?.mcp_enterprise?.mode === expectedMcpMode;
120
+
101
121
  const startOrReuseEnterpriseHttp = async (): Promise<{
102
122
  host: string;
103
123
  port: number;
104
124
  startedByThisProcess: boolean;
105
125
  }> => {
126
+ const repoRoot = process.cwd();
127
+ const expectedMcpMode = resolveMcpEnterpriseExperimentalFeature().mode;
106
128
  const host = process.env.PUMUKI_ENTERPRISE_MCP_HOST ?? '127.0.0.1';
107
129
  const parsedPort = Number.parseInt(process.env.PUMUKI_ENTERPRISE_MCP_PORT ?? '', 10);
108
130
  const preferredPort = Number.isFinite(parsedPort) ? parsedPort : 7391;
@@ -110,16 +132,21 @@ const startOrReuseEnterpriseHttp = async (): Promise<{
110
132
 
111
133
  const healthUrl = `http://${host}:${requestedPort}/health`;
112
134
  try {
113
- const health = (await fetchJson(healthUrl)) as { status?: string };
114
- if (health.status === 'ok') {
135
+ const health = (await fetchJson(healthUrl)) as EnterpriseHealthPayload;
136
+ if (canReuseEnterpriseHttp(health, repoRoot, expectedMcpMode)) {
115
137
  return {
116
138
  host,
117
139
  port: requestedPort,
118
140
  startedByThisProcess: false,
119
141
  };
120
142
  }
121
- } catch {
122
- // Intentionally ignored: endpoint not available yet.
143
+ } catch (error) {
144
+ if (process.env.PUMUKI_DEBUG_MCP === '1') {
145
+ const message = error instanceof Error ? error.message : String(error);
146
+ process.stderr.write(
147
+ `[pumuki-mcp-enterprise-stdio] health probe reuse skipped: ${message}\n`
148
+ );
149
+ }
123
150
  }
124
151
 
125
152
  const portInUse = await isPortInUse(host, requestedPort);
@@ -127,7 +154,7 @@ const startOrReuseEnterpriseHttp = async (): Promise<{
127
154
  startEnterpriseMcpServer({
128
155
  host,
129
156
  port: resolvedPort,
130
- repoRoot: process.cwd(),
157
+ repoRoot,
131
158
  });
132
159
 
133
160
  return {
@@ -30,7 +30,10 @@ export type PolicyAsCodeTraceMetadata = {
30
30
  type PolicyAsCodeContract = {
31
31
  version: '1.0';
32
32
  source: PolicyProfileSource;
33
- signatures: Record<SkillsStage, string>;
33
+ signatures: Partial<Record<SkillsStage, string>> &
34
+ Record<'PRE_COMMIT' | 'PRE_PUSH' | 'CI', string>;
35
+ strict?: Partial<Record<SkillsStage, boolean>> &
36
+ Record<'PRE_COMMIT' | 'PRE_PUSH' | 'CI', boolean>;
34
37
  expires_at?: string;
35
38
  };
36
39
 
@@ -57,6 +60,40 @@ const policyStrictModeFromEnv = (): boolean => {
57
60
  return raw === '1' || raw === 'true' || raw === 'yes' || raw === 'on';
58
61
  };
59
62
 
63
+ const isBoolean = (value: unknown): value is boolean => {
64
+ return typeof value === 'boolean';
65
+ };
66
+
67
+ const resolveContractSignatureForStage = (
68
+ signatures: PolicyAsCodeContract['signatures'],
69
+ stage: SkillsStage
70
+ ): string | undefined => {
71
+ if (stage === 'PRE_WRITE') {
72
+ return signatures.PRE_WRITE ?? signatures.PRE_COMMIT;
73
+ }
74
+ return signatures[stage];
75
+ };
76
+
77
+ const resolveContractStrictForStage = (
78
+ strictByStage: PolicyAsCodeContract['strict'],
79
+ stage: SkillsStage
80
+ ): boolean | null => {
81
+ if (!strictByStage) {
82
+ return null;
83
+ }
84
+ if (stage === 'PRE_WRITE') {
85
+ return strictByStage.PRE_WRITE ?? strictByStage.PRE_COMMIT ?? null;
86
+ }
87
+ return strictByStage[stage] ?? null;
88
+ };
89
+
90
+ const resolvePolicyStrict = (strictByContract: boolean | null): boolean => {
91
+ if (typeof strictByContract === 'boolean') {
92
+ return strictByContract;
93
+ }
94
+ return policyStrictModeFromEnv();
95
+ };
96
+
60
97
  const isPolicyAsCodeContract = (value: unknown): value is PolicyAsCodeContract => {
61
98
  if (!isObject(value)) {
62
99
  return false;
@@ -74,10 +111,21 @@ const isPolicyAsCodeContract = (value: unknown): value is PolicyAsCodeContract =
74
111
  if (!isObject(value.signatures)) {
75
112
  return false;
76
113
  }
114
+ if (
115
+ typeof value.strict !== 'undefined' &&
116
+ (!isObject(value.strict)
117
+ || (typeof value.strict.PRE_WRITE !== 'undefined' && !isBoolean(value.strict.PRE_WRITE))
118
+ || !isBoolean(value.strict.PRE_COMMIT)
119
+ || !isBoolean(value.strict.PRE_PUSH)
120
+ || !isBoolean(value.strict.CI))
121
+ ) {
122
+ return false;
123
+ }
77
124
  if (typeof value.expires_at !== 'undefined' && !isIsoDateString(value.expires_at)) {
78
125
  return false;
79
126
  }
80
127
  return (
128
+ (typeof value.signatures.PRE_WRITE === 'undefined' || isSha256Hex(value.signatures.PRE_WRITE)) &&
81
129
  isSha256Hex(value.signatures.PRE_COMMIT) &&
82
130
  isSha256Hex(value.signatures.PRE_PUSH) &&
83
131
  isSha256Hex(value.signatures.CI)
@@ -111,7 +159,7 @@ export const resolvePolicyAsCodeTraceMetadata = (params: {
111
159
  hash: string;
112
160
  repoRoot: string;
113
161
  }): PolicyAsCodeTraceMetadata => {
114
- const strict = policyStrictModeFromEnv();
162
+ const envStrict = policyStrictModeFromEnv();
115
163
  const computedVersion = `policy-as-code/${params.source}@${POLICY_AS_CODE_VERSION}`;
116
164
  const computedSignature = createPolicyAsCodeSignature({
117
165
  stage: params.stage,
@@ -123,7 +171,7 @@ export const resolvePolicyAsCodeTraceMetadata = (params: {
123
171
  const contractPath = join(params.repoRoot, POLICY_AS_CODE_CONTRACT_PATH);
124
172
 
125
173
  if (!existsSync(contractPath)) {
126
- if (strict) {
174
+ if (envStrict) {
127
175
  return {
128
176
  version: computedVersion,
129
177
  signature: computedSignature,
@@ -133,7 +181,7 @@ export const resolvePolicyAsCodeTraceMetadata = (params: {
133
181
  code: 'POLICY_AS_CODE_UNSIGNED',
134
182
  message:
135
183
  'Policy-as-code contract is missing; runtime policy metadata is unsigned.',
136
- strict,
184
+ strict: envStrict,
137
185
  },
138
186
  };
139
187
  }
@@ -146,7 +194,7 @@ export const resolvePolicyAsCodeTraceMetadata = (params: {
146
194
  status: 'valid',
147
195
  code: 'POLICY_AS_CODE_VALID',
148
196
  message: 'Policy-as-code metadata generated from active runtime policy.',
149
- strict,
197
+ strict: envStrict,
150
198
  },
151
199
  };
152
200
  }
@@ -162,15 +210,17 @@ export const resolvePolicyAsCodeTraceMetadata = (params: {
162
210
  status: 'invalid',
163
211
  code: 'POLICY_AS_CODE_CONTRACT_INVALID',
164
212
  message: 'Policy-as-code contract is malformed.',
165
- strict,
213
+ strict: envStrict,
166
214
  },
167
215
  };
168
216
  }
169
217
 
218
+ const strict = resolvePolicyStrict(resolveContractStrictForStage(raw.strict, params.stage));
219
+
170
220
  if (raw.source !== params.source) {
171
221
  return {
172
222
  version: `policy-as-code/${raw.source}@${raw.version}`,
173
- signature: raw.signatures[params.stage],
223
+ signature: resolveContractSignatureForStage(raw.signatures, params.stage) ?? computedSignature,
174
224
  policySource: `file:${POLICY_AS_CODE_CONTRACT_PATH}`,
175
225
  validation: {
176
226
  status: 'unknown-source',
@@ -183,18 +233,18 @@ export const resolvePolicyAsCodeTraceMetadata = (params: {
183
233
  }
184
234
 
185
235
  const expectedSignature = createPolicyAsCodeSignature({
186
- stage: params.stage,
236
+ stage: params.stage === 'PRE_WRITE' ? 'PRE_COMMIT' : params.stage,
187
237
  source: params.source,
188
238
  bundle: params.bundle,
189
239
  hash: params.hash,
190
240
  version: raw.version,
191
241
  });
192
- const stageSignature = raw.signatures[params.stage];
242
+ const stageSignature = resolveContractSignatureForStage(raw.signatures, params.stage);
193
243
 
194
244
  if (stageSignature !== expectedSignature) {
195
245
  return {
196
246
  version: `policy-as-code/${raw.source}@${raw.version}`,
197
- signature: stageSignature,
247
+ signature: stageSignature ?? computedSignature,
198
248
  policySource: `file:${POLICY_AS_CODE_CONTRACT_PATH}`,
199
249
  validation: {
200
250
  status: 'invalid',
@@ -211,7 +261,7 @@ export const resolvePolicyAsCodeTraceMetadata = (params: {
211
261
  if (Number.isFinite(expiresAtTimestamp) && Date.now() >= expiresAtTimestamp) {
212
262
  return {
213
263
  version: `policy-as-code/${raw.source}@${raw.version}`,
214
- signature: stageSignature,
264
+ signature: stageSignature ?? computedSignature,
215
265
  policySource: `file:${POLICY_AS_CODE_CONTRACT_PATH}`,
216
266
  validation: {
217
267
  status: 'expired',
@@ -225,7 +275,7 @@ export const resolvePolicyAsCodeTraceMetadata = (params: {
225
275
 
226
276
  return {
227
277
  version: `policy-as-code/${raw.source}@${raw.version}`,
228
- signature: stageSignature,
278
+ signature: stageSignature ?? computedSignature,
229
279
  policySource: `file:${POLICY_AS_CODE_CONTRACT_PATH}`,
230
280
  validation: {
231
281
  status: 'valid',
@@ -243,7 +293,7 @@ export const resolvePolicyAsCodeTraceMetadata = (params: {
243
293
  status: 'invalid',
244
294
  code: 'POLICY_AS_CODE_CONTRACT_INVALID',
245
295
  message: 'Policy-as-code contract cannot be parsed as JSON.',
246
- strict,
296
+ strict: envStrict,
247
297
  },
248
298
  };
249
299
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pumuki",
3
- "version": "6.3.75",
3
+ "version": "6.3.77",
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": {