pumuki 6.3.170 → 6.3.172
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/CHANGELOG.md +0 -44
- package/README.md +342 -161
- package/VERSION +1 -1
- package/core/facts/detectors/text/ios.test.ts +0 -36
- package/core/facts/detectors/text/ios.ts +0 -24
- package/core/facts/detectors/typescript/index.ts +18 -40
- package/core/facts/extractHeuristicFacts.ts +0 -1
- package/docs/operations/RELEASE_NOTES.md +0 -6
- package/integrations/config/coreSkillsLock.ts +1 -11
- package/integrations/config/skillsRuleSet.ts +1 -0
- package/integrations/git/stageRunners.ts +0 -20
- package/integrations/lifecycle/audit.ts +74 -11
- package/integrations/lifecycle/bootstrapManifest.ts +2 -12
- package/integrations/lifecycle/governanceObservationSnapshot.ts +5 -2
- package/integrations/lifecycle/install.ts +0 -21
- package/integrations/mcp/alignedPlatformGate.ts +2 -6
- package/integrations/mcp/evidenceStdioServer.cli.ts +33 -76
- package/package.json +2 -2
- package/assets/readme/current/01-menu-consumer-real.png +0 -0
- package/assets/readme/current/02-option1-audit-block-real.png +0 -0
- package/assets/readme/current/03-option5-pattern-checks-real.png +0 -0
|
@@ -840,14 +840,6 @@ const hasSwiftXCTestOnlyBrownfieldSuiteUsage = (source: string): boolean => {
|
|
|
840
840
|
return !hasSwiftTestingImportUsage(source) && !hasSwiftTestingSuiteAttributeUsage(source);
|
|
841
841
|
};
|
|
842
842
|
|
|
843
|
-
const hasSwiftXCTestHelperOrFactoryUsage = (source: string): boolean => {
|
|
844
|
-
return (
|
|
845
|
-
hasSwiftXCTestImportUsage(source) &&
|
|
846
|
-
!hasSwiftXCTestCaseSubclassUsage(source) &&
|
|
847
|
-
!hasSwiftLegacyXCTestMethodUsage(source)
|
|
848
|
-
);
|
|
849
|
-
};
|
|
850
|
-
|
|
851
843
|
export const hasSwiftLegacyXCTestImportUsage = (source: string): boolean => {
|
|
852
844
|
if (!hasSwiftXCTestImportUsage(source)) {
|
|
853
845
|
return false;
|
|
@@ -893,10 +885,6 @@ export const hasSwiftXCTestAssertionUsage = (source: string): boolean => {
|
|
|
893
885
|
return false;
|
|
894
886
|
}
|
|
895
887
|
|
|
896
|
-
if (hasSwiftXCTestHelperOrFactoryUsage(source)) {
|
|
897
|
-
return false;
|
|
898
|
-
}
|
|
899
|
-
|
|
900
888
|
if (hasSwiftXCTestOnlyBrownfieldSuiteUsage(source)) {
|
|
901
889
|
return false;
|
|
902
890
|
}
|
|
@@ -912,10 +900,6 @@ export const hasSwiftXCTUnwrapUsage = (source: string): boolean => {
|
|
|
912
900
|
return false;
|
|
913
901
|
}
|
|
914
902
|
|
|
915
|
-
if (hasSwiftXCTestHelperOrFactoryUsage(source)) {
|
|
916
|
-
return false;
|
|
917
|
-
}
|
|
918
|
-
|
|
919
903
|
if (hasSwiftXCTestOnlyBrownfieldSuiteUsage(source)) {
|
|
920
904
|
return false;
|
|
921
905
|
}
|
|
@@ -932,10 +916,6 @@ const hasSwiftConfirmationUsage = (source: string): boolean => {
|
|
|
932
916
|
};
|
|
933
917
|
|
|
934
918
|
export const hasSwiftWaitForExpectationsUsage = (source: string): boolean => {
|
|
935
|
-
if (hasSwiftXCTestHelperOrFactoryUsage(source)) {
|
|
936
|
-
return false;
|
|
937
|
-
}
|
|
938
|
-
|
|
939
919
|
const legacyWaitPattern = /\bwait\s*\(\s*for\s*:|\bwaitForExpectations\s*\(/;
|
|
940
920
|
return collectSwiftFunctionDeclarations(source).some((declaration) => {
|
|
941
921
|
if (!/\basync\b/.test(declaration.signature)) {
|
|
@@ -947,10 +927,6 @@ export const hasSwiftWaitForExpectationsUsage = (source: string): boolean => {
|
|
|
947
927
|
};
|
|
948
928
|
|
|
949
929
|
export const hasSwiftLegacyExpectationDescriptionUsage = (source: string): boolean => {
|
|
950
|
-
if (hasSwiftXCTestHelperOrFactoryUsage(source)) {
|
|
951
|
-
return false;
|
|
952
|
-
}
|
|
953
|
-
|
|
954
930
|
return collectSwiftFunctionDeclarations(source).some((declaration) => {
|
|
955
931
|
if (!/\basync\b/.test(declaration.signature)) {
|
|
956
932
|
return false;
|
|
@@ -340,10 +340,7 @@ type AstNodeWithAncestors = {
|
|
|
340
340
|
ancestors: readonly AstNode[];
|
|
341
341
|
};
|
|
342
342
|
|
|
343
|
-
const toPositiveLine = (node:
|
|
344
|
-
if (!isObject(node)) {
|
|
345
|
-
return null;
|
|
346
|
-
}
|
|
343
|
+
const toPositiveLine = (node: AstNode): number | null => {
|
|
347
344
|
const loc = node.loc;
|
|
348
345
|
if (!isObject(loc)) {
|
|
349
346
|
return null;
|
|
@@ -748,18 +745,15 @@ const toSemanticMemberNode = (
|
|
|
748
745
|
};
|
|
749
746
|
};
|
|
750
747
|
|
|
751
|
-
const toSingleLineArray = (line: number | null
|
|
748
|
+
const toSingleLineArray = (line: number | null): readonly number[] | undefined => {
|
|
752
749
|
return typeof line === 'number' ? [line] : undefined;
|
|
753
750
|
};
|
|
754
751
|
|
|
755
752
|
const dedupeSemanticNodes = (
|
|
756
|
-
nodes: ReadonlyArray<TypeScriptSemanticNode
|
|
753
|
+
nodes: ReadonlyArray<TypeScriptSemanticNode>
|
|
757
754
|
): readonly TypeScriptSemanticNode[] => {
|
|
758
755
|
const unique = new Map<string, TypeScriptSemanticNode>();
|
|
759
756
|
for (const node of nodes) {
|
|
760
|
-
if (!node) {
|
|
761
|
-
continue;
|
|
762
|
-
}
|
|
763
757
|
const key = `${node.kind}:${node.name}:${(node.lines ?? []).join(',')}`;
|
|
764
758
|
if (!unique.has(key)) {
|
|
765
759
|
unique.set(key, node);
|
|
@@ -3361,10 +3355,7 @@ const buildDtoBoundaryMatch = (node: unknown): TypeScriptDtoBoundaryMatch | unde
|
|
|
3361
3355
|
|
|
3362
3356
|
const classNode = match.node;
|
|
3363
3357
|
const className = methodNameFromNode(classNode.id);
|
|
3364
|
-
const classBody =
|
|
3365
|
-
isObject(classNode.body) && classNode.body.type === 'ClassBody' && Array.isArray(classNode.body.body)
|
|
3366
|
-
? classNode.body.body
|
|
3367
|
-
: [];
|
|
3358
|
+
const classBody = isObject(classNode.body) && classNode.body.type === 'ClassBody' ? classNode.body.body : [];
|
|
3368
3359
|
const classLine = toPositiveLine(classNode);
|
|
3369
3360
|
|
|
3370
3361
|
const propertyEntries = classBody.filter((member) => {
|
|
@@ -3734,12 +3725,12 @@ const buildBackendTransactionsMatch = (
|
|
|
3734
3725
|
return undefined;
|
|
3735
3726
|
}
|
|
3736
3727
|
|
|
3737
|
-
const ownerNode
|
|
3728
|
+
const ownerNode = semanticOwnerFromAncestors(match.ancestors) ?? {
|
|
3738
3729
|
kind: 'member',
|
|
3739
3730
|
name: `${boundaryObjectName}.${boundaryName}`,
|
|
3740
3731
|
lines: toSingleLineArray(callLine),
|
|
3741
3732
|
};
|
|
3742
|
-
const primaryNode
|
|
3733
|
+
const primaryNode =
|
|
3743
3734
|
ownerNode.kind === 'class' || ownerNode.kind === 'member'
|
|
3744
3735
|
? ownerNode
|
|
3745
3736
|
: {
|
|
@@ -4163,7 +4154,7 @@ const buildRateLimitingThrottlerMatch = (
|
|
|
4163
4154
|
...(calleeName === 'Throttle'
|
|
4164
4155
|
? [
|
|
4165
4156
|
{
|
|
4166
|
-
kind: 'member'
|
|
4157
|
+
kind: 'member',
|
|
4167
4158
|
name: '@nestjs/throttler',
|
|
4168
4159
|
lines: toSingleLineArray(callLine),
|
|
4169
4160
|
},
|
|
@@ -4172,7 +4163,7 @@ const buildRateLimitingThrottlerMatch = (
|
|
|
4172
4163
|
...(calleeName === 'UseGuards'
|
|
4173
4164
|
? [
|
|
4174
4165
|
{
|
|
4175
|
-
kind: 'member'
|
|
4166
|
+
kind: 'member',
|
|
4176
4167
|
name: 'ThrottlerGuard',
|
|
4177
4168
|
lines: toSingleLineArray(callLine),
|
|
4178
4169
|
},
|
|
@@ -4181,7 +4172,7 @@ const buildRateLimitingThrottlerMatch = (
|
|
|
4181
4172
|
...(calleeName === 'forRoot' || calleeName === 'forRootAsync'
|
|
4182
4173
|
? [
|
|
4183
4174
|
{
|
|
4184
|
-
kind: 'member'
|
|
4175
|
+
kind: 'member',
|
|
4185
4176
|
name: 'ThrottlerModule',
|
|
4186
4177
|
lines: toSingleLineArray(callLine),
|
|
4187
4178
|
},
|
|
@@ -4566,7 +4557,7 @@ const buildCallbackHellPatternMatch = (
|
|
|
4566
4557
|
lines: toSingleLineArray(toPositiveLine(nestedCall)),
|
|
4567
4558
|
}
|
|
4568
4559
|
: undefined,
|
|
4569
|
-
]
|
|
4560
|
+
].filter((entry): entry is TypeScriptSemanticNode => entry !== undefined)
|
|
4570
4561
|
);
|
|
4571
4562
|
const lines = sortedUniqueLines([
|
|
4572
4563
|
...(primaryNode.lines ?? []),
|
|
@@ -4854,12 +4845,7 @@ const isRuntimeApiLiteral = (node: AstNode): boolean => {
|
|
|
4854
4845
|
};
|
|
4855
4846
|
|
|
4856
4847
|
const isNeutralHardcodedNumericLiteral = (node: AstNode): boolean => {
|
|
4857
|
-
return (
|
|
4858
|
-
node.type === 'NumericLiteral' &&
|
|
4859
|
-
(node.value === 0 ||
|
|
4860
|
-
node.value === 1 ||
|
|
4861
|
-
(typeof node.value === 'number' && node.value >= -32768 && node.value <= -32000))
|
|
4862
|
-
);
|
|
4848
|
+
return node.type === 'NumericLiteral' && (node.value === 0 || node.value === 1);
|
|
4863
4849
|
};
|
|
4864
4850
|
|
|
4865
4851
|
const isBenignHardcodedConfigLiteral = (node: AstNode): boolean => {
|
|
@@ -5055,12 +5041,7 @@ const buildEnvDefaultFallbackPatternMatch = (
|
|
|
5055
5041
|
if (value.operator !== '||' && value.operator !== '??') {
|
|
5056
5042
|
return false;
|
|
5057
5043
|
}
|
|
5058
|
-
|
|
5059
|
-
return (
|
|
5060
|
-
isObject(leftNode) &&
|
|
5061
|
-
typeof memberExpressionPropertyName(leftNode) === 'string' &&
|
|
5062
|
-
isProcessEnvBaseAccess(leftNode.object)
|
|
5063
|
-
);
|
|
5044
|
+
return typeof memberExpressionPropertyName(value.left) === 'string' && isProcessEnvBaseAccess(value.left.object);
|
|
5064
5045
|
}
|
|
5065
5046
|
|
|
5066
5047
|
if (value.type !== 'AssignmentPattern') {
|
|
@@ -5507,7 +5488,6 @@ const buildProductionMockMatch = (
|
|
|
5507
5488
|
return (
|
|
5508
5489
|
isObject(objectNode) &&
|
|
5509
5490
|
objectNode.type === 'Identifier' &&
|
|
5510
|
-
typeof objectNode.name === 'string' &&
|
|
5511
5491
|
productionMockLibraryPattern.test(objectNode.name) &&
|
|
5512
5492
|
isObject(propertyNode) &&
|
|
5513
5493
|
propertyNode.type === 'Identifier' &&
|
|
@@ -5664,7 +5644,7 @@ const buildExceptionFilterMatch = (
|
|
|
5664
5644
|
return methodNameFromNode(expression) === 'Catch';
|
|
5665
5645
|
});
|
|
5666
5646
|
|
|
5667
|
-
const primaryNode
|
|
5647
|
+
const primaryNode = {
|
|
5668
5648
|
kind: 'class',
|
|
5669
5649
|
name: className,
|
|
5670
5650
|
lines: typeof classLine === 'number' ? [classLine] : [],
|
|
@@ -5679,7 +5659,7 @@ const buildExceptionFilterMatch = (
|
|
|
5679
5659
|
...(hasCatchDecorator
|
|
5680
5660
|
? [
|
|
5681
5661
|
{
|
|
5682
|
-
kind: 'member'
|
|
5662
|
+
kind: 'member',
|
|
5683
5663
|
name: '@Catch',
|
|
5684
5664
|
lines: typeof classLine === 'number' ? [classLine] : [],
|
|
5685
5665
|
},
|
|
@@ -5755,8 +5735,7 @@ const buildGuardMatch = (node: unknown): TypeScriptGuardMatch | undefined => {
|
|
|
5755
5735
|
? classOrMemberNode.lines[0]
|
|
5756
5736
|
: toPositiveLine(classOrMemberNode ?? decoratorNode);
|
|
5757
5737
|
const decoratorLine = toPositiveLine(decoratorNode);
|
|
5758
|
-
const
|
|
5759
|
-
const guardArgument = expressionArguments.find((argument) => methodNameFromNode(argument) === 'JwtAuthGuard');
|
|
5738
|
+
const guardArgument = expression.arguments.find((argument) => methodNameFromNode(argument) === 'JwtAuthGuard');
|
|
5760
5739
|
const guardLine = toPositiveLine(guardArgument) ?? decoratorLine;
|
|
5761
5740
|
const ownerName =
|
|
5762
5741
|
classOrMemberNode?.kind === 'class' || classOrMemberNode?.kind === 'member'
|
|
@@ -5861,8 +5840,7 @@ const buildInterceptorMatch = (node: unknown): TypeScriptInterceptorMatch | unde
|
|
|
5861
5840
|
? classOrMemberNode.lines[0]
|
|
5862
5841
|
: toPositiveLine(classOrMemberNode ?? decoratorNode);
|
|
5863
5842
|
const decoratorLine = toPositiveLine(decoratorNode);
|
|
5864
|
-
const
|
|
5865
|
-
const interceptorArgument = expressionArguments.find((argument) => {
|
|
5843
|
+
const interceptorArgument = expression.arguments.find((argument) => {
|
|
5866
5844
|
const name = interceptorNameFromNode(argument);
|
|
5867
5845
|
return typeof name === 'string' && /Interceptor$/u.test(name);
|
|
5868
5846
|
});
|
|
@@ -6191,7 +6169,7 @@ const buildReactClassComponentMatch = (
|
|
|
6191
6169
|
lines: toSingleLineArray(reactImportInfo.importLines[0]),
|
|
6192
6170
|
}
|
|
6193
6171
|
: undefined,
|
|
6194
|
-
]);
|
|
6172
|
+
].filter((entry): entry is TypeScriptSemanticNode => entry !== undefined));
|
|
6195
6173
|
|
|
6196
6174
|
const lines = sortedUniqueLines([
|
|
6197
6175
|
...(typeof classLine === 'number' ? [classLine] : []),
|
|
@@ -6384,7 +6362,7 @@ const buildSingletonPatternMatch = (
|
|
|
6384
6362
|
lines: toSingleLineArray(toPositiveLine(singletonFactoryMethodMember)),
|
|
6385
6363
|
}
|
|
6386
6364
|
: undefined,
|
|
6387
|
-
]
|
|
6365
|
+
].filter((entry): entry is TypeScriptSemanticNode => entry !== undefined)
|
|
6388
6366
|
);
|
|
6389
6367
|
|
|
6390
6368
|
const lines = sortedUniqueLines([
|
|
@@ -460,7 +460,6 @@ type ASTDetectorRegistryEntry = {
|
|
|
460
460
|
readonly code: string;
|
|
461
461
|
readonly message: string;
|
|
462
462
|
readonly pathCheck?: (path: string) => boolean;
|
|
463
|
-
readonly excludePaths?: ReadonlyArray<(path: string) => boolean>;
|
|
464
463
|
readonly includeTestPaths?: boolean;
|
|
465
464
|
};
|
|
466
465
|
|
|
@@ -4,12 +4,6 @@ This file tracks the active deterministic framework line used in this repository
|
|
|
4
4
|
Canonical release chronology lives in `CHANGELOG.md`.
|
|
5
5
|
This file keeps only the operational highlights and rollout notes that matter while running the framework.
|
|
6
6
|
|
|
7
|
-
### 2026-05-07 (v6.3.170)
|
|
8
|
-
|
|
9
|
-
- **npm README entrypoint:** la ficha pública de npm pasa a mostrar el README premium real, con capturas actuales de menú/auditoría, límites explícitos de hooks/CI, binarios MCP stdio y cobertura AUTO vs DECLARATIVE de skills.
|
|
10
|
-
- **Release typecheck:** la línea publicada recupera `npm run typecheck` con fixes conservadores en detectores TypeScript y helpers lifecycle/MCP, sin cambiar semántica de detección.
|
|
11
|
-
- **Rollout:** publicar `pumuki@6.3.170` para sustituir el README antiguo visible en `pumuki@6.3.169` sin degradar la línea de paquete ya publicada.
|
|
12
|
-
|
|
13
7
|
### 2026-05-05 (v6.3.150)
|
|
14
8
|
|
|
15
9
|
- **RuralGo PUMUKI-INC-061:** las notificaciones de bloqueo ya recomiendan `policy reconcile --strict --apply --json` cuando la remediación real requiere converger policy-as-code.
|
|
@@ -8,15 +8,6 @@ const PACKAGE_ROOT = resolve(__dirname, '..', '..');
|
|
|
8
8
|
|
|
9
9
|
let cachedCoreSkillsLock: SkillsLockV1 | undefined;
|
|
10
10
|
|
|
11
|
-
const writeDebugFallback = (error: unknown): void => {
|
|
12
|
-
if (process.env.PUMUKI_DEBUG !== '1') {
|
|
13
|
-
return;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
17
|
-
process.stderr.write(`[pumuki][skills-lock] compile fallback: ${message}\n`);
|
|
18
|
-
};
|
|
19
|
-
|
|
20
11
|
export const resolveCoreSkillsLockForPackageRoot = (
|
|
21
12
|
packageRoot: string
|
|
22
13
|
): SkillsLockV1 | undefined => {
|
|
@@ -29,8 +20,7 @@ export const resolveCoreSkillsLockForPackageRoot = (
|
|
|
29
20
|
if (compiledLock.bundles.length > 0) {
|
|
30
21
|
return compiledLock;
|
|
31
22
|
}
|
|
32
|
-
} catch
|
|
33
|
-
writeDebugFallback(error);
|
|
23
|
+
} catch {
|
|
34
24
|
}
|
|
35
25
|
|
|
36
26
|
return loadSkillsLock(packageRoot);
|
|
@@ -77,9 +77,6 @@ const shouldSkipRestagingTrackedEvidenceForDocumentationOnlyScope = (params: {
|
|
|
77
77
|
return paths.every(isDocumentationOnlyStagedPath);
|
|
78
78
|
};
|
|
79
79
|
|
|
80
|
-
const hasStagedEvidencePath = (paths: ReadonlyArray<string>): boolean =>
|
|
81
|
-
paths.some((path) => path === '.ai_evidence.json' || path === '.AI_EVIDENCE.json');
|
|
82
|
-
|
|
83
80
|
const PRE_COMMIT_EVIDENCE_MAX_AGE_SECONDS = 900;
|
|
84
81
|
const PRE_PUSH_EVIDENCE_MAX_AGE_SECONDS = 1800;
|
|
85
82
|
const HOOK_GATE_PROGRESS_REMINDER_MS = 2000;
|
|
@@ -523,7 +520,6 @@ const syncTrackedEvidenceAfterSuccessfulPreCommit = (params: {
|
|
|
523
520
|
dependencies: StageRunnerDependencies;
|
|
524
521
|
repoRoot: string;
|
|
525
522
|
gateBlocked: boolean;
|
|
526
|
-
evidenceWasStagedAtStart: boolean;
|
|
527
523
|
}): boolean => {
|
|
528
524
|
const evidenceAbsolutePath = join(params.repoRoot, EVIDENCE_FILE_PATH);
|
|
529
525
|
if (!existsSync(evidenceAbsolutePath)) {
|
|
@@ -547,20 +543,6 @@ const syncTrackedEvidenceAfterSuccessfulPreCommit = (params: {
|
|
|
547
543
|
params.dependencies.restorePathFromHead(params.repoRoot, EVIDENCE_FILE_PATH);
|
|
548
544
|
return false;
|
|
549
545
|
}
|
|
550
|
-
if (
|
|
551
|
-
!params.gateBlocked &&
|
|
552
|
-
!params.evidenceWasStagedAtStart &&
|
|
553
|
-
!isTruthyEnvFlag(process.env.PUMUKI_PRE_COMMIT_ALWAYS_RESTAGE_TRACKED_EVIDENCE)
|
|
554
|
-
) {
|
|
555
|
-
if (!params.dependencies.isQuietMode()) {
|
|
556
|
-
process.stderr.write(
|
|
557
|
-
`[pumuki][evidence-sync] tracked ${EVIDENCE_FILE_PATH} restored because it was not staged before PRE_COMMIT. ` +
|
|
558
|
-
`Force previous behavior: PUMUKI_PRE_COMMIT_ALWAYS_RESTAGE_TRACKED_EVIDENCE=1\n`
|
|
559
|
-
);
|
|
560
|
-
}
|
|
561
|
-
params.dependencies.restorePathFromHead(params.repoRoot, EVIDENCE_FILE_PATH);
|
|
562
|
-
return false;
|
|
563
|
-
}
|
|
564
546
|
try {
|
|
565
547
|
params.dependencies.stagePath(params.repoRoot, EVIDENCE_FILE_PATH);
|
|
566
548
|
return false;
|
|
@@ -805,7 +787,6 @@ export async function runPreCommitStage(
|
|
|
805
787
|
const activeDependencies = getDependencies(dependencies);
|
|
806
788
|
const repoRoot = activeDependencies.resolveRepoRoot();
|
|
807
789
|
const manifestSnapshot = captureManifestGuardSnapshot(repoRoot);
|
|
808
|
-
const initiallyStagedPaths = activeDependencies.listStagedIndexPaths(repoRoot);
|
|
809
790
|
activeDependencies.ensureRuntimeArtifactsIgnored(repoRoot);
|
|
810
791
|
if (
|
|
811
792
|
enforceGitAtomicityGate({
|
|
@@ -839,7 +820,6 @@ export async function runPreCommitStage(
|
|
|
839
820
|
dependencies: activeDependencies,
|
|
840
821
|
repoRoot,
|
|
841
822
|
gateBlocked: result.exitCode !== 0,
|
|
842
|
-
evidenceWasStagedAtStart: hasStagedEvidencePath(initiallyStagedPaths),
|
|
843
823
|
})
|
|
844
824
|
) {
|
|
845
825
|
return 1;
|
|
@@ -4,7 +4,7 @@ import { GitService, type IGitService } from '../git/GitService';
|
|
|
4
4
|
import { hasAllowedExtension } from '../git/gitDiffUtils';
|
|
5
5
|
import { runPlatformGate } from '../git/runPlatformGate';
|
|
6
6
|
import { evaluatePlatformGateFindings } from '../git/runPlatformGateEvaluation';
|
|
7
|
-
import { DEFAULT_FACT_FILE_EXTENSIONS } from '../git/runPlatformGateFacts';
|
|
7
|
+
import { DEFAULT_FACT_FILE_EXTENSIONS, type GateScope } from '../git/runPlatformGateFacts';
|
|
8
8
|
import { resolvePolicyForStage, type ResolvedStagePolicy } from '../gate/stagePolicies';
|
|
9
9
|
|
|
10
10
|
export type LifecycleAuditStage = 'PRE_WRITE' | 'PRE_COMMIT' | 'PRE_PUSH' | 'CI';
|
|
@@ -23,7 +23,7 @@ export type LifecycleAuditResult = {
|
|
|
23
23
|
command: 'pumuki audit';
|
|
24
24
|
repo_root: string;
|
|
25
25
|
stage: LifecycleAuditStage;
|
|
26
|
-
scope: { kind: 'repo' };
|
|
26
|
+
scope: { kind: 'repo' | 'staged'; staged_matching_extensions_count: number };
|
|
27
27
|
audit_mode: 'gate' | 'engine';
|
|
28
28
|
gate_exit_code: number;
|
|
29
29
|
files_scanned: number | null;
|
|
@@ -70,6 +70,28 @@ const countUntrackedMatchingExtensions = (
|
|
|
70
70
|
.filter((path) => hasAllowedExtension(path, extensions)).length;
|
|
71
71
|
};
|
|
72
72
|
|
|
73
|
+
const collectStagedMatchingExtensions = (
|
|
74
|
+
git: Pick<IGitService, 'resolveRepoRoot' | 'runGit'>,
|
|
75
|
+
extensions: ReadonlyArray<string>
|
|
76
|
+
): string[] => {
|
|
77
|
+
const repoRoot = git.resolveRepoRoot();
|
|
78
|
+
return git.runGit(['diff', '--cached', '--name-only'], repoRoot)
|
|
79
|
+
.split('\n')
|
|
80
|
+
.map((line) => line.trim())
|
|
81
|
+
.filter((line) => line.length > 0)
|
|
82
|
+
.filter((path) => hasAllowedExtension(path, extensions));
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const resolveLifecycleAuditScope = (params: {
|
|
86
|
+
stage: LifecycleAuditStage;
|
|
87
|
+
stagedMatchingExtensions: ReadonlyArray<string>;
|
|
88
|
+
}): GateScope => {
|
|
89
|
+
if (params.stage === 'PRE_WRITE' && params.stagedMatchingExtensions.length > 0) {
|
|
90
|
+
return { kind: 'staged' };
|
|
91
|
+
}
|
|
92
|
+
return { kind: 'repo' };
|
|
93
|
+
};
|
|
94
|
+
|
|
73
95
|
const isFindingBlocking = (finding: SnapshotFinding): boolean => {
|
|
74
96
|
return finding.severity === 'CRITICAL' ||
|
|
75
97
|
finding.severity === 'ERROR' ||
|
|
@@ -159,6 +181,30 @@ const buildRuleIdNormalization = (params: {
|
|
|
159
181
|
};
|
|
160
182
|
};
|
|
161
183
|
|
|
184
|
+
const isScopedPreWriteGlobalEnforcementOnly = (params: {
|
|
185
|
+
stage: LifecycleAuditStage;
|
|
186
|
+
scope: GateScope;
|
|
187
|
+
findings: ReadonlyArray<LifecycleAuditFinding>;
|
|
188
|
+
}): boolean =>
|
|
189
|
+
params.stage === 'PRE_WRITE' &&
|
|
190
|
+
params.scope.kind === 'staged' &&
|
|
191
|
+
params.findings.length > 0 &&
|
|
192
|
+
params.findings.every(
|
|
193
|
+
(finding) => finding.code === 'SKILLS_GLOBAL_ENFORCEMENT_INCOMPLETE_CRITICAL'
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
const toScopedAuditAdvisoryFinding = (
|
|
197
|
+
finding: LifecycleAuditFinding
|
|
198
|
+
): LifecycleAuditFinding => ({
|
|
199
|
+
...finding,
|
|
200
|
+
severity: 'INFO',
|
|
201
|
+
code: 'AUDIT_SCOPED_GLOBAL_ENFORCEMENT_ADVISORY',
|
|
202
|
+
message:
|
|
203
|
+
'Scoped PRE_WRITE audit evaluated only the staged supported files; global skills enforcement debt is retained as advisory for repo-wide work. ' +
|
|
204
|
+
finding.message,
|
|
205
|
+
blocking: false,
|
|
206
|
+
});
|
|
207
|
+
|
|
162
208
|
export const runLifecycleAudit = async (params: {
|
|
163
209
|
stage: LifecycleAuditStage;
|
|
164
210
|
auditMode: 'gate' | 'engine';
|
|
@@ -179,13 +225,18 @@ export const runLifecycleAudit = async (params: {
|
|
|
179
225
|
);
|
|
180
226
|
const extensions = DEFAULT_FACT_FILE_EXTENSIONS;
|
|
181
227
|
const untrackedMatchingExtensionsCount = countUntrackedMatchingExtensions(git, extensions);
|
|
228
|
+
const stagedMatchingExtensions = collectStagedMatchingExtensions(git, extensions);
|
|
229
|
+
const scope = resolveLifecycleAuditScope({
|
|
230
|
+
stage: params.stage,
|
|
231
|
+
stagedMatchingExtensions,
|
|
232
|
+
});
|
|
182
233
|
|
|
183
234
|
const gateParams =
|
|
184
235
|
params.auditMode === 'engine'
|
|
185
236
|
? {
|
|
186
237
|
policy: resolved.policy,
|
|
187
238
|
policyTrace: resolved.trace,
|
|
188
|
-
scope
|
|
239
|
+
scope,
|
|
189
240
|
silent: true,
|
|
190
241
|
auditMode: 'engine' as const,
|
|
191
242
|
dependencies: {
|
|
@@ -204,12 +255,12 @@ export const runLifecycleAudit = async (params: {
|
|
|
204
255
|
: {
|
|
205
256
|
policy: resolved.policy,
|
|
206
257
|
policyTrace: resolved.trace,
|
|
207
|
-
scope
|
|
258
|
+
scope,
|
|
208
259
|
silent: true,
|
|
209
260
|
auditMode: 'gate' as const,
|
|
210
261
|
};
|
|
211
262
|
|
|
212
|
-
const
|
|
263
|
+
const rawGateExitCode = await activeDependencies.runPlatformGate(gateParams);
|
|
213
264
|
const evidence = activeDependencies.readEvidence(repoRoot);
|
|
214
265
|
const filesScanned =
|
|
215
266
|
typeof evidence?.snapshot.files_scanned === 'number' &&
|
|
@@ -220,29 +271,41 @@ export const runLifecycleAudit = async (params: {
|
|
|
220
271
|
typeof evidence?.snapshot.outcome === 'string' ? evidence.snapshot.outcome : null;
|
|
221
272
|
const findings = extractAuditFindings({
|
|
222
273
|
evidence,
|
|
223
|
-
gateExitCode,
|
|
274
|
+
gateExitCode: rawGateExitCode,
|
|
224
275
|
stage: params.stage,
|
|
225
276
|
snapshotOutcome,
|
|
226
277
|
});
|
|
278
|
+
const scopedPreWriteGlobalEnforcementOnly = isScopedPreWriteGlobalEnforcementOnly({
|
|
279
|
+
stage: params.stage,
|
|
280
|
+
scope,
|
|
281
|
+
findings,
|
|
282
|
+
});
|
|
283
|
+
const effectiveFindings = scopedPreWriteGlobalEnforcementOnly
|
|
284
|
+
? findings.map(toScopedAuditAdvisoryFinding)
|
|
285
|
+
: findings;
|
|
286
|
+
const gateExitCode = scopedPreWriteGlobalEnforcementOnly ? 0 : rawGateExitCode;
|
|
227
287
|
|
|
228
288
|
return {
|
|
229
289
|
command: 'pumuki audit',
|
|
230
290
|
repo_root: repoRoot,
|
|
231
291
|
stage: params.stage,
|
|
232
|
-
scope: {
|
|
292
|
+
scope: {
|
|
293
|
+
kind: scope.kind === 'staged' ? 'staged' : 'repo',
|
|
294
|
+
staged_matching_extensions_count: stagedMatchingExtensions.length,
|
|
295
|
+
},
|
|
233
296
|
audit_mode: params.auditMode,
|
|
234
297
|
gate_exit_code: gateExitCode,
|
|
235
298
|
files_scanned: filesScanned,
|
|
236
299
|
untracked_matching_extensions_count: untrackedMatchingExtensionsCount,
|
|
237
300
|
snapshot_outcome: snapshotOutcome,
|
|
238
|
-
findings_count:
|
|
239
|
-
blocking_findings_count:
|
|
301
|
+
findings_count: effectiveFindings.length,
|
|
302
|
+
blocking_findings_count: effectiveFindings.filter((finding) => finding.blocking).length,
|
|
240
303
|
rules_coverage: evidence?.snapshot.rules_coverage ?? null,
|
|
241
304
|
rule_id_normalization: buildRuleIdNormalization({
|
|
242
|
-
findings,
|
|
305
|
+
findings: effectiveFindings,
|
|
243
306
|
rulesCoverage: evidence?.snapshot.rules_coverage,
|
|
244
307
|
}),
|
|
245
|
-
findings,
|
|
308
|
+
findings: effectiveFindings,
|
|
246
309
|
policy_reconcile_hint: POLICY_RECONCILE_HINT,
|
|
247
310
|
};
|
|
248
311
|
};
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
2
2
|
import { dirname, join } from 'node:path'
|
|
3
|
-
import type { AiGateStage } from '../gate/evaluateAiGate'
|
|
4
3
|
import { getPumukiHooksStatus, resolvePumukiHooksDirectory } from './hookManager'
|
|
5
4
|
import { LifecycleGitService, type ILifecycleGitService } from './gitService'
|
|
6
5
|
import {
|
|
@@ -16,15 +15,6 @@ import { readLifecycleState } from './state'
|
|
|
16
15
|
|
|
17
16
|
export const BOOTSTRAP_MANIFEST_RELATIVE_PATH = '.pumuki/bootstrap-manifest.json'
|
|
18
17
|
|
|
19
|
-
const JSON_PRETTY_PRINT_SPACES = 2
|
|
20
|
-
|
|
21
|
-
const toAiGateStage = (stage: string | undefined): AiGateStage => {
|
|
22
|
-
if (stage === 'PRE_WRITE' || stage === 'PRE_COMMIT' || stage === 'PRE_PUSH' || stage === 'CI') {
|
|
23
|
-
return stage
|
|
24
|
-
}
|
|
25
|
-
return 'PRE_WRITE'
|
|
26
|
-
}
|
|
27
|
-
|
|
28
18
|
type AdapterCommandContract = {
|
|
29
19
|
path: string
|
|
30
20
|
present: boolean
|
|
@@ -184,7 +174,7 @@ export const buildLifecycleBootstrapManifest = (params: {
|
|
|
184
174
|
})
|
|
185
175
|
const governanceNextAction = readGovernanceNextAction({
|
|
186
176
|
repoRoot,
|
|
187
|
-
stage:
|
|
177
|
+
stage: governanceObservation.evidence.snapshot_stage ?? 'PRE_WRITE',
|
|
188
178
|
governanceObservation,
|
|
189
179
|
})
|
|
190
180
|
const hooksDirectory = resolvePumukiHooksDirectory(repoRoot)
|
|
@@ -233,7 +223,7 @@ export const buildLifecycleBootstrapManifest = (params: {
|
|
|
233
223
|
}
|
|
234
224
|
|
|
235
225
|
const serializeManifest = (manifest: LifecycleBootstrapManifest): string =>
|
|
236
|
-
`${JSON.stringify(manifest, null,
|
|
226
|
+
`${JSON.stringify(manifest, null, 2)}\n`
|
|
237
227
|
|
|
238
228
|
export const writeLifecycleBootstrapManifest = (params: {
|
|
239
229
|
repoRoot: string
|
|
@@ -10,6 +10,7 @@ import type { ILifecycleGitService } from './gitService';
|
|
|
10
10
|
import { LifecycleGitService } from './gitService';
|
|
11
11
|
import type { LifecyclePolicyValidationSnapshot } from './policyValidationSnapshot';
|
|
12
12
|
import { writeInfo } from './cliOutputs';
|
|
13
|
+
import { formatTrackingActionableContext } from './trackingState';
|
|
13
14
|
|
|
14
15
|
const DEFAULT_PROTECTED_BRANCHES = new Set(['main', 'master', 'develop', 'dev']);
|
|
15
16
|
const STRICT_ENV_VALUE = 'strict';
|
|
@@ -205,8 +206,9 @@ const buildHints = (
|
|
|
205
206
|
hints.push(`Falta el tracking canónico declarado (${tracking.canonical_path ?? 'sin resolver'}).`);
|
|
206
207
|
}
|
|
207
208
|
if (tracking.enforced && tracking.single_in_progress_valid === false) {
|
|
209
|
+
const actionableContext = formatTrackingActionableContext(tracking);
|
|
208
210
|
hints.push(
|
|
209
|
-
`El tracking canónico debe dejar exactamente una 🚧 (actual=${tracking.in_progress_count ?? 'n/a'}).`
|
|
211
|
+
`El tracking canónico debe dejar exactamente una 🚧 (actual=${tracking.in_progress_count ?? 'n/a'}${actionableContext ? `, ${actionableContext}` : ''}).`
|
|
210
212
|
);
|
|
211
213
|
}
|
|
212
214
|
hints.push('SDD/OpenSpec: usa PUMUKI_EXPERIMENTAL_SDD=advisory|strict cuando el loop SDD esté activo.');
|
|
@@ -349,8 +351,9 @@ export const buildGovernanceObservationSummaryLines = (
|
|
|
349
351
|
lines.push(`Attention: ${snapshot.attention_codes.join(', ')}`);
|
|
350
352
|
}
|
|
351
353
|
if (snapshot.tracking.enforced && snapshot.tracking.single_in_progress_valid === false) {
|
|
354
|
+
const actionableContext = formatTrackingActionableContext(snapshot.tracking);
|
|
352
355
|
lines.push(
|
|
353
|
-
`Tracking: canonical=${snapshot.tracking.canonical_path ?? 'unknown'} in_progress_count=${snapshot.tracking.in_progress_count ?? 'n/a'}`
|
|
356
|
+
`Tracking: canonical=${snapshot.tracking.canonical_path ?? 'unknown'} in_progress_count=${snapshot.tracking.in_progress_count ?? 'n/a'}${actionableContext ? ` ${actionableContext}` : ''}`
|
|
354
357
|
);
|
|
355
358
|
}
|
|
356
359
|
return lines;
|
|
@@ -13,7 +13,6 @@ import { createEmptyEvaluationMetrics } from '../evidence/evaluationMetrics';
|
|
|
13
13
|
import { readOpenSpecManagedArtifacts, writeLifecycleState } from './state';
|
|
14
14
|
import { ensureRuntimeArtifactsIgnored } from './artifacts';
|
|
15
15
|
import { runLifecycleAdapterInstall } from './adapter';
|
|
16
|
-
import { writeLifecycleBootstrapManifest } from './bootstrapManifest';
|
|
17
16
|
import { runPolicyReconcile } from './policyReconcile';
|
|
18
17
|
import { emitGateBlockedNotification } from '../notifications/emitAuditSummaryNotification';
|
|
19
18
|
|
|
@@ -21,10 +20,6 @@ export type LifecycleInstallResult = {
|
|
|
21
20
|
repoRoot: string;
|
|
22
21
|
version: string;
|
|
23
22
|
changedHooks: ReadonlyArray<string>;
|
|
24
|
-
bootstrapManifest: {
|
|
25
|
-
path: string;
|
|
26
|
-
changed: boolean;
|
|
27
|
-
};
|
|
28
23
|
openSpecBootstrap?: OpenSpecBootstrapResult;
|
|
29
24
|
degradedDoctorBypass?: boolean;
|
|
30
25
|
};
|
|
@@ -132,18 +127,10 @@ export const runLifecycleInstall = (params?: {
|
|
|
132
127
|
});
|
|
133
128
|
ensureRepoBaselineAdapter(report.repoRoot);
|
|
134
129
|
materializeStrictPolicyAsCode(report.repoRoot);
|
|
135
|
-
const bootstrapManifest = writeLifecycleBootstrapManifest({
|
|
136
|
-
git,
|
|
137
|
-
repoRoot: report.repoRoot,
|
|
138
|
-
});
|
|
139
130
|
return {
|
|
140
131
|
repoRoot: report.repoRoot,
|
|
141
132
|
version,
|
|
142
133
|
changedHooks,
|
|
143
|
-
bootstrapManifest: {
|
|
144
|
-
path: bootstrapManifest.path,
|
|
145
|
-
changed: bootstrapManifest.changed,
|
|
146
|
-
},
|
|
147
134
|
openSpecBootstrap: undefined,
|
|
148
135
|
degradedDoctorBypass: true,
|
|
149
136
|
};
|
|
@@ -192,19 +179,11 @@ export const runLifecycleInstall = (params?: {
|
|
|
192
179
|
});
|
|
193
180
|
ensureRepoBaselineAdapter(report.repoRoot);
|
|
194
181
|
materializeStrictPolicyAsCode(report.repoRoot);
|
|
195
|
-
const bootstrapManifest = writeLifecycleBootstrapManifest({
|
|
196
|
-
git,
|
|
197
|
-
repoRoot: report.repoRoot,
|
|
198
|
-
});
|
|
199
182
|
|
|
200
183
|
return {
|
|
201
184
|
repoRoot: report.repoRoot,
|
|
202
185
|
version,
|
|
203
186
|
changedHooks,
|
|
204
|
-
bootstrapManifest: {
|
|
205
|
-
path: bootstrapManifest.path,
|
|
206
|
-
changed: bootstrapManifest.changed,
|
|
207
|
-
},
|
|
208
187
|
openSpecBootstrap,
|
|
209
188
|
};
|
|
210
189
|
};
|
|
@@ -52,14 +52,10 @@ const resolvePrePushBootstrapBaseRefInRepo = (repoRoot: string): string => {
|
|
|
52
52
|
continue;
|
|
53
53
|
}
|
|
54
54
|
try {
|
|
55
|
-
const
|
|
55
|
+
const changedFiles = runGit(
|
|
56
56
|
repoRoot,
|
|
57
57
|
['diff', '--name-only', '--diff-filter=ACMR', `${candidate}..HEAD`]
|
|
58
|
-
)
|
|
59
|
-
if (diffOutput === null) {
|
|
60
|
-
continue;
|
|
61
|
-
}
|
|
62
|
-
const changedFiles = diffOutput
|
|
58
|
+
)
|
|
63
59
|
.split('\n')
|
|
64
60
|
.map((line) => line.trim())
|
|
65
61
|
.filter((line) => line.length > 0).length;
|