pumuki 6.3.269 → 6.3.271
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 +4 -0
- package/VERSION +1 -1
- package/core/facts/detectors/text/android.test.ts +538 -0
- package/core/facts/detectors/text/android.ts +436 -0
- package/core/facts/detectors/text/ios.test.ts +328 -1
- package/core/facts/detectors/text/ios.ts +241 -0
- package/core/facts/detectors/typescript/index.test.ts +393 -0
- package/core/facts/detectors/typescript/index.ts +316 -0
- package/core/facts/extractHeuristicFacts.ts +70 -1
- package/core/rules/presets/heuristics/android.test.ts +91 -1
- package/core/rules/presets/heuristics/android.ts +360 -0
- package/core/rules/presets/heuristics/ios.test.ts +54 -1
- package/core/rules/presets/heuristics/ios.ts +243 -2
- package/core/rules/presets/heuristics/typescript.test.ts +50 -2
- package/core/rules/presets/heuristics/typescript.ts +162 -0
- package/docs/operations/RELEASE_NOTES.md +4 -0
- package/integrations/config/skillsDetectorRegistry.ts +501 -0
- package/integrations/config/skillsRuleClassification.ts +127 -3
- package/integrations/context/contextGate.ts +192 -0
- package/integrations/git/runPlatformGate.ts +4 -1
- package/integrations/lifecycle/preWriteAutomation.ts +1 -0
- package/integrations/lifecycle/preWriteLease.ts +41 -4
- package/package.json +2 -1
- package/scripts/classify-skills-rules.ts +2 -2
- package/scripts/framework-menu-consumer-actions-lib.ts +9 -9
- package/scripts/framework-menu-consumer-runtime-actions.ts +53 -117
- package/scripts/framework-menu-consumer-runtime-audit.ts +66 -0
- package/scripts/framework-menu-consumer-runtime-menu.ts +4 -4
- package/scripts/framework-menu-gate-lib.ts +86 -1
- package/scripts/framework-menu-layout-data.ts +3 -3
- package/scripts/framework-menu-legacy-audit-render-sections.ts +6 -0
- package/scripts/framework-menu.ts +10 -6
- package/scripts/package-install-smoke-consumer-npm-lib.ts +10 -4
- package/scripts/package-install-smoke-lifecycle-lib.ts +19 -0
- package/scripts/package-manifest-lib.ts +1 -0
|
@@ -3,7 +3,7 @@ import { resolveMappedHeuristicRuleIdsForCompiledRule } from './skillsDetectorRe
|
|
|
3
3
|
|
|
4
4
|
export type SkillsRuleClassificationStatus =
|
|
5
5
|
| 'AST_IMPLEMENTED'
|
|
6
|
-
| '
|
|
6
|
+
| 'IMPLEMENTAR_DETECTOR'
|
|
7
7
|
| 'REQUIERE_ESTUDIO'
|
|
8
8
|
| 'NO_ES_REGLA_DE_CODIGO';
|
|
9
9
|
|
|
@@ -28,7 +28,7 @@ export type SkillsRuleClassificationSummary = {
|
|
|
28
28
|
|
|
29
29
|
const emptyStatusCounts = (): Record<SkillsRuleClassificationStatus, number> => ({
|
|
30
30
|
AST_IMPLEMENTED: 0,
|
|
31
|
-
|
|
31
|
+
IMPLEMENTAR_DETECTOR: 0,
|
|
32
32
|
REQUIERE_ESTUDIO: 0,
|
|
33
33
|
NO_ES_REGLA_DE_CODIGO: 0,
|
|
34
34
|
});
|
|
@@ -55,11 +55,106 @@ const NON_CODE_RULE_PATTERNS: ReadonlyArray<RegExp> = [
|
|
|
55
55
|
/\bci\/cd\b/,
|
|
56
56
|
];
|
|
57
57
|
|
|
58
|
+
const CODE_RULE_WITH_DIRECT_DETECTOR_PATTERNS: ReadonlyArray<RegExp> = [
|
|
59
|
+
/\baccessibility\b/,
|
|
60
|
+
/\bcontentdescription\b/,
|
|
61
|
+
/\bsemantic\b/,
|
|
62
|
+
/\btest tag\b/,
|
|
63
|
+
/\basynctask\b/,
|
|
64
|
+
/\bcallback\b/,
|
|
65
|
+
/\bglobal ?scope\b/,
|
|
66
|
+
/\brunblocking\b/,
|
|
67
|
+
/\bthread\.?sleep\b/,
|
|
68
|
+
/\bdispatch(queue|group|semaphore)\b/,
|
|
69
|
+
/\bforce\b.*\b(unwrap|try|cast)\b/,
|
|
70
|
+
/\btry!\b/,
|
|
71
|
+
/\bas!\b/,
|
|
72
|
+
/\bany\b/,
|
|
73
|
+
/\bconsole\b/,
|
|
74
|
+
/\blog\b/,
|
|
75
|
+
/\bprint\b/,
|
|
76
|
+
/\bsecret\b/,
|
|
77
|
+
/\btoken\b/,
|
|
78
|
+
/\bpassword\b/,
|
|
79
|
+
/\bapi key\b/,
|
|
80
|
+
/\bhardcoded\b/,
|
|
81
|
+
/\bmagic number\b/,
|
|
82
|
+
/\bcolor\b/,
|
|
83
|
+
/\bdimension\b/,
|
|
84
|
+
/\bgod\b/,
|
|
85
|
+
/\blarge\b.*\b(class|component|view|service|function|file)\b/,
|
|
86
|
+
/\bsolid\b/,
|
|
87
|
+
/\bsrp\b/,
|
|
88
|
+
/\bdependency\b.*\binjection\b/,
|
|
89
|
+
/\bconstructor\b.*\b(param|dependency)/,
|
|
90
|
+
/\bsharedpreferences\b/,
|
|
91
|
+
/\buserdefaults\b/,
|
|
92
|
+
/\bappstorage\b/,
|
|
93
|
+
/\bkeychain\b/,
|
|
94
|
+
/\braw sql\b/,
|
|
95
|
+
/\bsql\b.*\b(template|injection)\b/,
|
|
96
|
+
/\bempty catch\b/,
|
|
97
|
+
/\bcatch\b.*\b(empty|silenc)/,
|
|
98
|
+
/\bmock\b/,
|
|
99
|
+
/\bspy\b/,
|
|
100
|
+
/\bjunit4\b/,
|
|
101
|
+
/\bxctassert\b/,
|
|
102
|
+
/\bxctunwrap\b/,
|
|
103
|
+
/\bxctest\b/,
|
|
104
|
+
/\bquick\b/,
|
|
105
|
+
/\bnimble\b/,
|
|
106
|
+
/\bwaitforexpectations\b/,
|
|
107
|
+
/\bexpectation\(description\b/,
|
|
108
|
+
/\bnavigationview\b/,
|
|
109
|
+
/\bgeometryreader\b/,
|
|
110
|
+
/\banyview\b/,
|
|
111
|
+
/\bforegroundcolor\b/,
|
|
112
|
+
/\bcornerradius\b/,
|
|
113
|
+
/\bsheet\b.*\bispresented\b/,
|
|
114
|
+
/\bscrollview\b.*\bshowsindicators\b/,
|
|
115
|
+
/\bforeach\b.*\b(indices|index)\b/,
|
|
116
|
+
/\bonchange\b/,
|
|
117
|
+
/\bontapgesture\b/,
|
|
118
|
+
/\buiscreen\.main\.bounds\b/,
|
|
119
|
+
/\bstring\(format\b/,
|
|
120
|
+
/\bobservableobject\b/,
|
|
121
|
+
/\bstateobject\b/,
|
|
122
|
+
/\bobservable\b/,
|
|
123
|
+
/\blivedata\b/,
|
|
124
|
+
/\bstateflow\b/,
|
|
125
|
+
/\bsharedflow\b/,
|
|
126
|
+
/\bremember\b/,
|
|
127
|
+
/\blaunched(effect)?\b/,
|
|
128
|
+
/\blazy(column|row|vstack|hstack)\b/,
|
|
129
|
+
/\bwindow ?size ?class\b/,
|
|
130
|
+
/\bpadding\b/,
|
|
131
|
+
/\bframe\b/,
|
|
132
|
+
/\bfont\b/,
|
|
133
|
+
/\broute\b/,
|
|
134
|
+
/\bnavigation\b/,
|
|
135
|
+
];
|
|
136
|
+
|
|
137
|
+
const STUDY_BEFORE_DETECTOR_RULE_IDS = new Set<string>([
|
|
138
|
+
'skills.android.guideline.android.adaptive-layouts-responsive-design-windowsizeclass',
|
|
139
|
+
'skills.android.guideline.android.color-contrast-wcag-aa-mi-nimo',
|
|
140
|
+
'skills.android.guideline.android.play-console-production-deployment',
|
|
141
|
+
'skills.backend.guideline.backend.dependency-injection-injectable-inject-providers-array',
|
|
142
|
+
'skills.backend.guideline.backend.event-store-log-de-eventos-para-auditori-a',
|
|
143
|
+
]);
|
|
144
|
+
|
|
145
|
+
const requiresStudyBeforeDetector = (rule: SkillsCompiledRule): boolean =>
|
|
146
|
+
STUDY_BEFORE_DETECTOR_RULE_IDS.has(rule.id);
|
|
147
|
+
|
|
58
148
|
const isNonCodeRule = (rule: SkillsCompiledRule): boolean => {
|
|
59
149
|
const haystack = normalizeText(`${rule.id} ${rule.description} ${rule.sourceSkill}`);
|
|
60
150
|
return NON_CODE_RULE_PATTERNS.some((pattern) => pattern.test(haystack));
|
|
61
151
|
};
|
|
62
152
|
|
|
153
|
+
const hasDirectCodeDetectorCandidate = (rule: SkillsCompiledRule): boolean => {
|
|
154
|
+
const haystack = normalizeText(`${rule.id} ${rule.description} ${rule.sourceSkill}`);
|
|
155
|
+
return CODE_RULE_WITH_DIRECT_DETECTOR_PATTERNS.some((pattern) => pattern.test(haystack));
|
|
156
|
+
};
|
|
157
|
+
|
|
63
158
|
const classifyRule = (rule: SkillsCompiledRule): ClassifiedSkillsRule => {
|
|
64
159
|
const evaluationMode = rule.evaluationMode ?? 'AUTO';
|
|
65
160
|
const astNodeIds = resolveMappedHeuristicRuleIdsForCompiledRule(rule);
|
|
@@ -86,12 +181,27 @@ const classifyRule = (rule: SkillsCompiledRule): ClassifiedSkillsRule => {
|
|
|
86
181
|
sourcePath: rule.sourcePath,
|
|
87
182
|
evaluationMode,
|
|
88
183
|
severity: rule.severity,
|
|
89
|
-
status: '
|
|
184
|
+
status: 'IMPLEMENTAR_DETECTOR',
|
|
90
185
|
reason: 'AUTO rule without AST/nodal detector binding; implement detector mapping.',
|
|
91
186
|
astNodeIds,
|
|
92
187
|
};
|
|
93
188
|
}
|
|
94
189
|
|
|
190
|
+
if (requiresStudyBeforeDetector(rule)) {
|
|
191
|
+
return {
|
|
192
|
+
ruleId: rule.id,
|
|
193
|
+
platform: rule.platform,
|
|
194
|
+
sourceSkill: rule.sourceSkill,
|
|
195
|
+
sourcePath: rule.sourcePath,
|
|
196
|
+
evaluationMode,
|
|
197
|
+
severity: rule.severity,
|
|
198
|
+
status: 'REQUIERE_ESTUDIO',
|
|
199
|
+
reason:
|
|
200
|
+
'Rule has code relevance, but the AST/nodal signal needs explicit design before implementation to avoid poor umbrella detectors.',
|
|
201
|
+
astNodeIds,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
95
205
|
if (isNonCodeRule(rule)) {
|
|
96
206
|
return {
|
|
97
207
|
ruleId: rule.id,
|
|
@@ -106,6 +216,20 @@ const classifyRule = (rule: SkillsCompiledRule): ClassifiedSkillsRule => {
|
|
|
106
216
|
};
|
|
107
217
|
}
|
|
108
218
|
|
|
219
|
+
if (hasDirectCodeDetectorCandidate(rule)) {
|
|
220
|
+
return {
|
|
221
|
+
ruleId: rule.id,
|
|
222
|
+
platform: rule.platform,
|
|
223
|
+
sourceSkill: rule.sourceSkill,
|
|
224
|
+
sourcePath: rule.sourcePath,
|
|
225
|
+
evaluationMode,
|
|
226
|
+
severity: rule.severity,
|
|
227
|
+
status: 'IMPLEMENTAR_DETECTOR',
|
|
228
|
+
reason: 'Code rule has a direct AST/nodal detection surface; implement detector and blocking evidence.',
|
|
229
|
+
astNodeIds,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
109
233
|
return {
|
|
110
234
|
ruleId: rule.id,
|
|
111
235
|
platform: rule.platform,
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import type { Finding } from '../../core/gate/Finding';
|
|
4
|
+
import type { GateStage } from '../../core/gate/GateStage';
|
|
5
|
+
|
|
6
|
+
export type ContextGateStatus = 'applied' | 'blocked' | 'disabled';
|
|
7
|
+
|
|
8
|
+
export type ContextGateResult = {
|
|
9
|
+
status: ContextGateStatus;
|
|
10
|
+
finding?: Finding;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const CONTEXT_FILE = '.pumuki/context/context.json';
|
|
14
|
+
const CONTEXT_GATE_ENV = 'PUMUKI_CONTEXT_GATE';
|
|
15
|
+
const CONTEXT_SCHEMA_VERSION = 1;
|
|
16
|
+
|
|
17
|
+
const isStrictContextGate = (): boolean => {
|
|
18
|
+
const normalized = process.env[CONTEXT_GATE_ENV]?.trim().toLowerCase();
|
|
19
|
+
return normalized !== 'off' && normalized !== '0' && normalized !== 'false' && normalized !== 'disabled';
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
type LocalContextDocument = {
|
|
23
|
+
schema_version: number;
|
|
24
|
+
repo_root: string;
|
|
25
|
+
generated_at: string;
|
|
26
|
+
contract: {
|
|
27
|
+
status: 'applied';
|
|
28
|
+
required_before_gate: true;
|
|
29
|
+
agent_instruction: string;
|
|
30
|
+
user_remediation: string;
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export type ContextLifecycleResult = {
|
|
35
|
+
status: 'ok' | 'blocked';
|
|
36
|
+
path: string;
|
|
37
|
+
message: string;
|
|
38
|
+
document?: LocalContextDocument;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const buildContextDocument = (repoRoot: string): LocalContextDocument => ({
|
|
42
|
+
schema_version: CONTEXT_SCHEMA_VERSION,
|
|
43
|
+
repo_root: repoRoot,
|
|
44
|
+
generated_at: new Date().toISOString(),
|
|
45
|
+
contract: {
|
|
46
|
+
status: 'applied',
|
|
47
|
+
required_before_gate: true,
|
|
48
|
+
agent_instruction:
|
|
49
|
+
'STOP si este contexto no se puede leer o validar antes de editar, auditar, commitear o pushear.',
|
|
50
|
+
user_remediation:
|
|
51
|
+
'Ejecuta `npx pumuki context repair` y reejecuta el gate antes de continuar.',
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const contextPathForRepo = (repoRoot: string): string => join(repoRoot, CONTEXT_FILE);
|
|
56
|
+
|
|
57
|
+
const parseContextDocument = (repoRoot: string): LocalContextDocument => {
|
|
58
|
+
const contextPath = contextPathForRepo(repoRoot);
|
|
59
|
+
const parsed: unknown = JSON.parse(readFileSync(contextPath, 'utf8'));
|
|
60
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
61
|
+
throw new Error(`${CONTEXT_FILE} no contiene un objeto JSON valido`);
|
|
62
|
+
}
|
|
63
|
+
const candidate = parsed as Partial<LocalContextDocument>;
|
|
64
|
+
if (candidate.schema_version !== CONTEXT_SCHEMA_VERSION) {
|
|
65
|
+
throw new Error(`${CONTEXT_FILE} tiene schema_version invalido`);
|
|
66
|
+
}
|
|
67
|
+
if (candidate.repo_root !== repoRoot) {
|
|
68
|
+
throw new Error(`${CONTEXT_FILE} pertenece a otro repo_root`);
|
|
69
|
+
}
|
|
70
|
+
if (
|
|
71
|
+
!candidate.contract ||
|
|
72
|
+
candidate.contract.status !== 'applied' ||
|
|
73
|
+
candidate.contract.required_before_gate !== true ||
|
|
74
|
+
typeof candidate.contract.agent_instruction !== 'string' ||
|
|
75
|
+
candidate.contract.agent_instruction.trim().length === 0 ||
|
|
76
|
+
typeof candidate.contract.user_remediation !== 'string' ||
|
|
77
|
+
candidate.contract.user_remediation.trim().length === 0
|
|
78
|
+
) {
|
|
79
|
+
throw new Error(`${CONTEXT_FILE} no declara contrato aplicado para usuario y agente IA`);
|
|
80
|
+
}
|
|
81
|
+
return candidate as LocalContextDocument;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export const writeLocalContext = (repoRoot: string): ContextLifecycleResult => {
|
|
85
|
+
const contextPath = contextPathForRepo(repoRoot);
|
|
86
|
+
const document = buildContextDocument(repoRoot);
|
|
87
|
+
mkdirSync(join(repoRoot, '.pumuki/context'), { recursive: true });
|
|
88
|
+
writeFileSync(contextPath, `${JSON.stringify(document, null, 2)}\n`, 'utf8');
|
|
89
|
+
return {
|
|
90
|
+
status: 'ok',
|
|
91
|
+
path: contextPath,
|
|
92
|
+
message: 'Contexto local Pumuki inicializado y aplicado.',
|
|
93
|
+
document,
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export const readLocalContextStatus = (repoRoot: string): ContextLifecycleResult => {
|
|
98
|
+
const contextPath = contextPathForRepo(repoRoot);
|
|
99
|
+
if (!existsSync(contextPath)) {
|
|
100
|
+
return {
|
|
101
|
+
status: 'blocked',
|
|
102
|
+
path: contextPath,
|
|
103
|
+
message:
|
|
104
|
+
`Pumuki bloqueado: falta ${CONTEXT_FILE}. Ejecuta ` +
|
|
105
|
+
'`npx pumuki context init` o `npx pumuki context repair`.',
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
const document = parseContextDocument(repoRoot);
|
|
110
|
+
return {
|
|
111
|
+
status: 'ok',
|
|
112
|
+
path: contextPath,
|
|
113
|
+
message: 'Contexto local Pumuki leido, validado y aplicado.',
|
|
114
|
+
document,
|
|
115
|
+
};
|
|
116
|
+
} catch (error) {
|
|
117
|
+
const reason = error instanceof Error ? error.message : 'context validation error';
|
|
118
|
+
return {
|
|
119
|
+
status: 'blocked',
|
|
120
|
+
path: contextPath,
|
|
121
|
+
message:
|
|
122
|
+
`Pumuki bloqueado: contexto local invalido. Causa: ${reason}. Ejecuta ` +
|
|
123
|
+
'`npx pumuki context repair`.',
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
export const repairLocalContext = (repoRoot: string): ContextLifecycleResult =>
|
|
129
|
+
writeLocalContext(repoRoot);
|
|
130
|
+
|
|
131
|
+
const contextBlockedFinding = (params: {
|
|
132
|
+
stage: GateStage;
|
|
133
|
+
code: string;
|
|
134
|
+
cause: string;
|
|
135
|
+
evidence: string;
|
|
136
|
+
}): Finding => ({
|
|
137
|
+
ruleId: 'governance.context.local-context-not-applied',
|
|
138
|
+
severity: 'ERROR',
|
|
139
|
+
code: params.code,
|
|
140
|
+
message:
|
|
141
|
+
`Pumuki bloqueado: contexto local obligatorio no aplicado en ${params.stage}. ` +
|
|
142
|
+
`Causa: ${params.cause}. ` +
|
|
143
|
+
`Evidencia: ${params.evidence}. ` +
|
|
144
|
+
'Usuario: ejecuta `npx pumuki context init` si el repo no tiene contexto, o `npx pumuki context repair` si existe pero no valida. ' +
|
|
145
|
+
'Agente IA: STOP. No edites archivos ni continues la tarea hasta leer, validar y aplicar el contexto local del repo.',
|
|
146
|
+
filePath: CONTEXT_FILE,
|
|
147
|
+
matchedBy: 'PumukiContextGate',
|
|
148
|
+
source: 'pumuki-context-store',
|
|
149
|
+
why: 'Pumuki no puede garantizar continuidad entre sesiones ni aplicar el contrato activo del repo sin contexto local validado.',
|
|
150
|
+
impact: 'El agente y el usuario podrian trabajar con task, contrato o evidencia stale.',
|
|
151
|
+
expected_fix:
|
|
152
|
+
'Inicializa o repara el contexto local: `npx pumuki context init` o `npx pumuki context repair`, y reejecuta el gate.',
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
export const evaluateContextGate = (params: {
|
|
156
|
+
repoRoot: string;
|
|
157
|
+
stage: GateStage;
|
|
158
|
+
}): ContextGateResult => {
|
|
159
|
+
if (!isStrictContextGate()) {
|
|
160
|
+
return { status: 'disabled' };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const contextPath = contextPathForRepo(params.repoRoot);
|
|
164
|
+
if (!existsSync(contextPath)) {
|
|
165
|
+
return {
|
|
166
|
+
status: 'blocked',
|
|
167
|
+
finding: contextBlockedFinding({
|
|
168
|
+
stage: params.stage,
|
|
169
|
+
code: 'CONTEXT_NOT_APPLIED',
|
|
170
|
+
cause: `No existe ${CONTEXT_FILE}`,
|
|
171
|
+
evidence: contextPath,
|
|
172
|
+
}),
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
parseContextDocument(params.repoRoot);
|
|
178
|
+
} catch (error) {
|
|
179
|
+
const reason = error instanceof Error ? error.message : 'JSON parse error';
|
|
180
|
+
return {
|
|
181
|
+
status: 'blocked',
|
|
182
|
+
finding: contextBlockedFinding({
|
|
183
|
+
stage: params.stage,
|
|
184
|
+
code: 'CONTEXT_INVALID',
|
|
185
|
+
cause: reason,
|
|
186
|
+
evidence: contextPath,
|
|
187
|
+
}),
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return { status: 'applied' };
|
|
192
|
+
};
|
|
@@ -286,6 +286,9 @@ const toSkillsDeclarativeRulesClassificationFinding = (params: {
|
|
|
286
286
|
declarativeRuleIds: ReadonlyArray<string>;
|
|
287
287
|
facts: ReadonlyArray<Fact>;
|
|
288
288
|
}): Finding | undefined => {
|
|
289
|
+
if (process.env.PUMUKI_ENFORCE_DECLARATIVE_RULE_CLASSIFICATION !== '1') {
|
|
290
|
+
return undefined;
|
|
291
|
+
}
|
|
289
292
|
if (params.declarativeRuleIds.length === 0) {
|
|
290
293
|
return undefined;
|
|
291
294
|
}
|
|
@@ -302,7 +305,7 @@ const toSkillsDeclarativeRulesClassificationFinding = (params: {
|
|
|
302
305
|
message:
|
|
303
306
|
`Skills declarative rules require AST/nodal classification at ${params.stage}: ` +
|
|
304
307
|
`total=${params.declarativeRuleIds.length} sample_rule_ids=[${sampleRuleIds}]. ` +
|
|
305
|
-
'Classify every rule as
|
|
308
|
+
'Classify every rule as IMPLEMENTAR_DETECTOR, REQUIERE_ESTUDIO, or NO_ES_REGLA_DE_CODIGO; code skills cannot remain as hidden declarative/advisory rules.',
|
|
306
309
|
filePath: 'skills.lock.json',
|
|
307
310
|
matchedBy: 'SkillsDeclarativeRulesClassificationGuard',
|
|
308
311
|
source: 'skills-declarative-rules-classification',
|
|
@@ -250,6 +250,7 @@ export const buildPreWriteAutomationTrace = async (params: {
|
|
|
250
250
|
const leaseResult = activeDependencies.writePreWriteLease({
|
|
251
251
|
repoRoot: params.repoRoot,
|
|
252
252
|
git,
|
|
253
|
+
allowExistingCodeChanges: true,
|
|
253
254
|
});
|
|
254
255
|
trace.actions.push({
|
|
255
256
|
action: 'write_prewrite_lease',
|
|
@@ -8,6 +8,7 @@ import { DEFAULT_FACT_FILE_EXTENSIONS } from '../git/runPlatformGateFacts';
|
|
|
8
8
|
export type PreWriteLease = {
|
|
9
9
|
version: '1';
|
|
10
10
|
kind: 'pumuki-pre-write-lease';
|
|
11
|
+
validation_mode?: 'clean-prewrite' | 'validated-diff';
|
|
11
12
|
repo_root: string;
|
|
12
13
|
head: string;
|
|
13
14
|
branch: string | null;
|
|
@@ -119,6 +120,10 @@ const parseLease = (raw: string): PreWriteLease | undefined => {
|
|
|
119
120
|
return {
|
|
120
121
|
version: '1',
|
|
121
122
|
kind: 'pumuki-pre-write-lease',
|
|
123
|
+
validation_mode:
|
|
124
|
+
value.validation_mode === 'validated-diff' || value.validation_mode === 'clean-prewrite'
|
|
125
|
+
? value.validation_mode
|
|
126
|
+
: undefined,
|
|
122
127
|
repo_root: value.repo_root,
|
|
123
128
|
head: value.head,
|
|
124
129
|
branch: typeof value.branch === 'string' ? value.branch : null,
|
|
@@ -186,6 +191,33 @@ export const readPreWriteLeaseStatus = (params: {
|
|
|
186
191
|
};
|
|
187
192
|
}
|
|
188
193
|
|
|
194
|
+
if (lease.validation_mode === 'validated-diff') {
|
|
195
|
+
const expectedPaths = [...lease.pre_change_code_paths].sort((left, right) => left.localeCompare(right));
|
|
196
|
+
const currentPaths = [...changedCodePaths].sort((left, right) => left.localeCompare(right));
|
|
197
|
+
if (
|
|
198
|
+
lease.pre_change_code_changes_count !== expectedPaths.length ||
|
|
199
|
+
expectedPaths.length !== currentPaths.length ||
|
|
200
|
+
expectedPaths.some((path, index) => path !== currentPaths[index])
|
|
201
|
+
) {
|
|
202
|
+
return {
|
|
203
|
+
valid: false,
|
|
204
|
+
code: 'PRE_WRITE_LEASE_DIRTY_AT_ISSUE',
|
|
205
|
+
path,
|
|
206
|
+
lease,
|
|
207
|
+
changedCodePaths,
|
|
208
|
+
message: 'PRE_WRITE lease was issued for a different validated code diff.',
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
valid: true,
|
|
214
|
+
code: 'PRE_WRITE_LEASE_VALID',
|
|
215
|
+
path,
|
|
216
|
+
lease,
|
|
217
|
+
changedCodePaths,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
189
221
|
if (lease.pre_change_code_changes_count !== 0 || lease.pre_change_code_paths.length !== 0) {
|
|
190
222
|
return {
|
|
191
223
|
valid: false,
|
|
@@ -210,10 +242,11 @@ export const writePreWriteLease = (params: {
|
|
|
210
242
|
repoRoot: string;
|
|
211
243
|
git: Pick<IGitService, 'runGit'>;
|
|
212
244
|
now?: Date;
|
|
245
|
+
allowExistingCodeChanges?: boolean;
|
|
213
246
|
}): PreWriteLeaseWriteResult => {
|
|
214
247
|
const path = resolvePreWriteLeasePath(params.repoRoot);
|
|
215
248
|
const changedCodePaths = collectPreWriteCodeChangePaths(params);
|
|
216
|
-
if (changedCodePaths.length > 0) {
|
|
249
|
+
if (changedCodePaths.length > 0 && !params.allowExistingCodeChanges) {
|
|
217
250
|
return {
|
|
218
251
|
path,
|
|
219
252
|
written: false,
|
|
@@ -229,13 +262,14 @@ export const writePreWriteLease = (params: {
|
|
|
229
262
|
const lease: PreWriteLease = {
|
|
230
263
|
version: '1',
|
|
231
264
|
kind: 'pumuki-pre-write-lease',
|
|
265
|
+
validation_mode: changedCodePaths.length > 0 ? 'validated-diff' : 'clean-prewrite',
|
|
232
266
|
repo_root: params.repoRoot,
|
|
233
267
|
head: resolveHead(params),
|
|
234
268
|
branch: resolveBranch(params),
|
|
235
269
|
issued_at: issuedAt.toISOString(),
|
|
236
270
|
expires_at: new Date(issuedAt.getTime() + PRE_WRITE_LEASE_TTL_MS).toISOString(),
|
|
237
|
-
pre_change_code_changes_count:
|
|
238
|
-
pre_change_code_paths:
|
|
271
|
+
pre_change_code_changes_count: changedCodePaths.length,
|
|
272
|
+
pre_change_code_paths: changedCodePaths,
|
|
239
273
|
};
|
|
240
274
|
mkdirSync(dirname(path), { recursive: true });
|
|
241
275
|
writeFileSync(path, `${JSON.stringify(lease, null, 2)}\n`, 'utf8');
|
|
@@ -245,7 +279,10 @@ export const writePreWriteLease = (params: {
|
|
|
245
279
|
valid: true,
|
|
246
280
|
code: 'PRE_WRITE_LEASE_WRITTEN',
|
|
247
281
|
changedCodePaths,
|
|
248
|
-
message:
|
|
282
|
+
message:
|
|
283
|
+
changedCodePaths.length > 0
|
|
284
|
+
? 'PRE_WRITE lease written for validated code diff.'
|
|
285
|
+
: 'PRE_WRITE lease written for clean code state.',
|
|
249
286
|
lease,
|
|
250
287
|
};
|
|
251
288
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pumuki",
|
|
3
|
-
"version": "6.3.
|
|
3
|
+
"version": "6.3.271",
|
|
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": {
|
|
@@ -239,6 +239,7 @@
|
|
|
239
239
|
"core/rules/presets/*.ts",
|
|
240
240
|
"core/rules/presets/heuristics/*.ts",
|
|
241
241
|
"integrations/config/*.ts",
|
|
242
|
+
"integrations/context/*.ts",
|
|
242
243
|
"integrations/evidence/*.ts",
|
|
243
244
|
"integrations/gate/*.ts",
|
|
244
245
|
"integrations/git/*.ts",
|
|
@@ -19,8 +19,8 @@ process.stdout.write(`${JSON.stringify({
|
|
|
19
19
|
AST_IMPLEMENTED: summary.rules
|
|
20
20
|
.filter((rule) => rule.status === 'AST_IMPLEMENTED')
|
|
21
21
|
.slice(0, 12),
|
|
22
|
-
|
|
23
|
-
.filter((rule) => rule.status === '
|
|
22
|
+
IMPLEMENTAR_DETECTOR: summary.rules
|
|
23
|
+
.filter((rule) => rule.status === 'IMPLEMENTAR_DETECTOR')
|
|
24
24
|
.slice(0, 12),
|
|
25
25
|
REQUIERE_ESTUDIO: summary.rules
|
|
26
26
|
.filter((rule) => rule.status === 'REQUIERE_ESTUDIO')
|
|
@@ -26,22 +26,22 @@ export const createConsumerLegacyMenuActions = (
|
|
|
26
26
|
return [
|
|
27
27
|
{
|
|
28
28
|
id: '1',
|
|
29
|
-
label: '
|
|
29
|
+
label: 'Full audit (repo analysis)',
|
|
30
30
|
execute: params.runFullAudit,
|
|
31
31
|
},
|
|
32
32
|
{
|
|
33
33
|
id: '2',
|
|
34
|
-
label: '
|
|
34
|
+
label: 'Strict REPO+STAGING (CI/CD)',
|
|
35
35
|
execute: params.runStrictRepoAndStaged,
|
|
36
36
|
},
|
|
37
37
|
{
|
|
38
38
|
id: '3',
|
|
39
|
-
label: '
|
|
39
|
+
label: 'Strict STAGING only (dev)',
|
|
40
40
|
execute: params.runStrictStagedOnly,
|
|
41
41
|
},
|
|
42
42
|
{
|
|
43
43
|
id: '4',
|
|
44
|
-
label: '
|
|
44
|
+
label: 'Standard CRITICAL/HIGH',
|
|
45
45
|
execute: params.runStandardCriticalHigh,
|
|
46
46
|
},
|
|
47
47
|
{
|
|
@@ -66,27 +66,27 @@ export const createConsumerLegacyMenuActions = (
|
|
|
66
66
|
},
|
|
67
67
|
{
|
|
68
68
|
id: '5',
|
|
69
|
-
label: '
|
|
69
|
+
label: 'Pattern checks',
|
|
70
70
|
execute: params.runPatternChecks,
|
|
71
71
|
},
|
|
72
72
|
{
|
|
73
73
|
id: '6',
|
|
74
|
-
label: '
|
|
74
|
+
label: 'ESLint Admin+Web',
|
|
75
75
|
execute: params.runEslintAudit,
|
|
76
76
|
},
|
|
77
77
|
{
|
|
78
78
|
id: '7',
|
|
79
|
-
label: '
|
|
79
|
+
label: 'AST Intelligence',
|
|
80
80
|
execute: params.runAstIntelligence,
|
|
81
81
|
},
|
|
82
82
|
{
|
|
83
83
|
id: '8',
|
|
84
|
-
label: 'Export
|
|
84
|
+
label: 'Export Markdown',
|
|
85
85
|
execute: params.runExportMarkdown,
|
|
86
86
|
},
|
|
87
87
|
{
|
|
88
88
|
id: '9',
|
|
89
|
-
label: '
|
|
89
|
+
label: 'File diagnostics',
|
|
90
90
|
execute: params.runFileDiagnostics,
|
|
91
91
|
},
|
|
92
92
|
{
|