pumuki 6.3.39 → 6.3.41
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/README.md +21 -12
- package/VERSION +1 -1
- package/core/gate/evaluateRules.test.ts +40 -0
- package/core/gate/evaluateRules.ts +7 -1
- package/core/rules/Consequence.ts +1 -0
- package/docs/CONFIGURATION.md +50 -0
- package/docs/INSTALLATION.md +38 -11
- package/docs/MCP_SERVERS.md +1 -1
- package/docs/README.md +1 -0
- package/docs/RELEASE_NOTES.md +58 -0
- package/docs/USAGE.md +191 -9
- package/docs/registro-maestro-de-seguimiento.md +2 -2
- package/docs/seguimiento-activo-pumuki-saas-supermercados.md +1629 -1
- package/docs/validation/README.md +2 -1
- package/docs/validation/ast-intelligence-roadmap.md +96 -0
- package/integrations/config/skillsCustomRules.ts +14 -0
- package/integrations/config/skillsDetectorRegistry.ts +11 -1
- package/integrations/config/skillsLock.ts +30 -0
- package/integrations/config/skillsMarkdownRules.ts +14 -3
- package/integrations/config/skillsRuleSet.ts +25 -3
- package/integrations/evidence/readEvidence.test.ts +3 -2
- package/integrations/evidence/readEvidence.ts +14 -4
- package/integrations/evidence/repoState.ts +10 -2
- package/integrations/evidence/schema.test.ts +3 -2
- package/integrations/evidence/schema.ts +3 -0
- package/integrations/evidence/writeEvidence.test.ts +3 -2
- package/integrations/gate/evaluateAiGate.ts +511 -2
- package/integrations/git/GitService.ts +5 -1
- package/integrations/git/astIntelligenceDualValidation.ts +275 -0
- package/integrations/git/gitAtomicity.ts +42 -9
- package/integrations/git/resolveGitRefs.ts +37 -0
- package/integrations/git/runPlatformGate.ts +228 -1
- package/integrations/git/runPlatformGateEvaluation.ts +4 -0
- package/integrations/git/stageRunners.ts +116 -2
- package/integrations/lifecycle/cli.ts +759 -22
- package/integrations/lifecycle/doctor.ts +62 -0
- package/integrations/lifecycle/index.ts +1 -0
- package/integrations/lifecycle/packageInfo.ts +25 -3
- package/integrations/lifecycle/policyReconcile.ts +304 -0
- package/integrations/lifecycle/preWriteAutomation.ts +42 -2
- package/integrations/lifecycle/watch.ts +365 -0
- package/integrations/mcp/aiGateCheck.ts +59 -2
- package/integrations/mcp/autoExecuteAiStart.ts +25 -1
- package/integrations/mcp/preFlightCheck.ts +13 -0
- package/integrations/sdd/evidenceScaffold.ts +223 -0
- package/integrations/sdd/index.ts +2 -0
- package/integrations/sdd/stateSync.ts +400 -0
- package/integrations/sdd/syncDocs.ts +97 -2
- package/package.json +4 -1
- package/scripts/backlog-action-reasons-lib.ts +38 -0
- package/scripts/backlog-id-issue-map-lib.ts +69 -0
- package/scripts/backlog-json-contract-lib.ts +3 -0
- package/scripts/framework-menu-consumer-preflight-lib.ts +6 -0
- package/scripts/framework-menu-system-notifications-lib.ts +66 -6
- package/scripts/package-install-smoke-command-resolution-lib.ts +64 -0
- package/scripts/package-install-smoke-consumer-npm-lib.ts +43 -0
- package/scripts/package-install-smoke-consumer-repo-setup-lib.ts +2 -0
- package/scripts/package-install-smoke-execution-steps-lib.ts +27 -9
- package/scripts/package-install-smoke-lifecycle-lib.ts +15 -4
- package/scripts/package-install-smoke-workspace-factory-lib.ts +4 -1
- package/scripts/reconcile-consumer-backlog-issues-lib.ts +651 -0
- package/scripts/reconcile-consumer-backlog-issues.ts +348 -0
- package/scripts/watch-consumer-backlog-lib.ts +465 -0
- package/scripts/watch-consumer-backlog.ts +326 -0
|
@@ -5,6 +5,7 @@ Este directorio contiene solo documentación oficial y estable de validación pa
|
|
|
5
5
|
## Documentación oficial vigente
|
|
6
6
|
|
|
7
7
|
- `adapter-hook-runtime-validation.md`
|
|
8
|
+
- `ast-intelligence-roadmap.md`
|
|
8
9
|
- `c022-phase-acceptance-contract.md`
|
|
9
10
|
- `detection-audit-baseline.md`
|
|
10
11
|
- `enterprise-consumer-isolation-policy.md`
|
|
@@ -14,7 +15,7 @@ Este directorio contiene solo documentación oficial y estable de validación pa
|
|
|
14
15
|
## Estado operativo actual
|
|
15
16
|
|
|
16
17
|
- Master de seguimiento: `docs/registro-maestro-de-seguimiento.md`.
|
|
17
|
-
- Plan activo: `docs/seguimiento-
|
|
18
|
+
- Plan activo: `docs/seguimiento-activo-pumuki-saas-supermercados.md`.
|
|
18
19
|
- Suite contractual enterprise (MVP): `npm run -s validation:contract-suite:enterprise`.
|
|
19
20
|
- Perfiles activos del reporte JSON:
|
|
20
21
|
- `minimal`
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# AST Intelligence por Nodos (RFC + PoC)
|
|
2
|
+
|
|
3
|
+
Estado: implementado en modo incremental (issue `#616`).
|
|
4
|
+
|
|
5
|
+
## Objetivo
|
|
6
|
+
|
|
7
|
+
Evolucionar de validación heurística clásica a validación AST por nodos, multilenguaje y trazable desde reglas compiladas de skills.
|
|
8
|
+
|
|
9
|
+
## Alcance implementado (PoC v1)
|
|
10
|
+
|
|
11
|
+
- Modo dual de validación legacy + AST (`off/shadow/strict`).
|
|
12
|
+
- Comparación determinista por regla `skills.*` contra nodos `ast_nodes=[...]` compilados en runtime (`skills-ir:*`).
|
|
13
|
+
- Métricas de divergencia por ejecución:
|
|
14
|
+
- `mapped_rules`
|
|
15
|
+
- `divergences`
|
|
16
|
+
- `false_positives`
|
|
17
|
+
- `false_negatives`
|
|
18
|
+
- `latency_ms`
|
|
19
|
+
- `languages`
|
|
20
|
+
- Integración directa en gate de `PRE_COMMIT`, `PRE_PUSH`, `CI`.
|
|
21
|
+
|
|
22
|
+
PoC cubre lenguajes críticos ya usados en el runtime actual:
|
|
23
|
+
|
|
24
|
+
- TypeScript
|
|
25
|
+
- Swift
|
|
26
|
+
- Kotlin
|
|
27
|
+
|
|
28
|
+
## Arquitectura objetivo (30/60/90)
|
|
29
|
+
|
|
30
|
+
### 0-30 días (P0)
|
|
31
|
+
|
|
32
|
+
- Consolidar modo dual como baseline estable:
|
|
33
|
+
- `off`: desactivado.
|
|
34
|
+
- `shadow`: compara y reporta sin bloquear.
|
|
35
|
+
- `strict`: bloquea si hay divergencias.
|
|
36
|
+
- Mantener compatibilidad total con rulepacks existentes.
|
|
37
|
+
- Publicar métricas mínimas para auditoría de divergencias.
|
|
38
|
+
|
|
39
|
+
### 31-60 días (P1)
|
|
40
|
+
|
|
41
|
+
- Backends AST por lenguaje con extractor dedicado (TS/Swift/Kotlin en prioridad).
|
|
42
|
+
- Trazabilidad 1:1 `skill rule -> ast node -> finding`.
|
|
43
|
+
- Telemetría con series temporales de FP/FN por stage.
|
|
44
|
+
|
|
45
|
+
### 61-90 días (P2)
|
|
46
|
+
|
|
47
|
+
- Rollout gradual por porcentaje/repositorio.
|
|
48
|
+
- Políticas por stage para strict automático cuando FP/FN estén bajo umbral.
|
|
49
|
+
- Contratos de regresión multi-repo (fixtures enterprise).
|
|
50
|
+
|
|
51
|
+
## Contrato de compatibilidad (legacy + nuevo)
|
|
52
|
+
|
|
53
|
+
- El modo legacy sigue siendo la referencia de bloqueo por defecto.
|
|
54
|
+
- El modo AST entra en:
|
|
55
|
+
- `shadow` para medir paridad.
|
|
56
|
+
- `strict` para enforcement cuando la paridad sea aceptable.
|
|
57
|
+
- No se rompe compatibilidad con bundles/rules actuales; el modo dual usa la metadata runtime `skills-ir:*`.
|
|
58
|
+
|
|
59
|
+
## Rollout y rollback
|
|
60
|
+
|
|
61
|
+
### Rollout
|
|
62
|
+
|
|
63
|
+
1. Activar en `shadow`:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
PUMUKI_AST_INTELLIGENCE_DUAL_MODE=shadow
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
2. Observar divergencias por stage/repositorio.
|
|
70
|
+
3. Promover a `strict` cuando divergencias y latencia estén dentro de umbral.
|
|
71
|
+
|
|
72
|
+
### Rollback inmediato
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
PUMUKI_AST_INTELLIGENCE_DUAL_MODE=off
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Umbrales recomendados para pasar a strict
|
|
79
|
+
|
|
80
|
+
- `false_positives` y `false_negatives` cerca de 0 de forma sostenida.
|
|
81
|
+
- `latency_ms` aceptable para hooks locales.
|
|
82
|
+
- Sin regresiones en `test:stage-gates`.
|
|
83
|
+
|
|
84
|
+
## Evidencia técnica de esta entrega
|
|
85
|
+
|
|
86
|
+
- Runtime:
|
|
87
|
+
- `integrations/git/astIntelligenceDualValidation.ts`
|
|
88
|
+
- `integrations/git/runPlatformGate.ts`
|
|
89
|
+
- `integrations/git/runPlatformGateEvaluation.ts`
|
|
90
|
+
- Tests:
|
|
91
|
+
- `integrations/git/__tests__/astIntelligenceDualValidation.test.ts`
|
|
92
|
+
- `integrations/git/__tests__/runPlatformGateAstIntelligenceDualMode.test.ts`
|
|
93
|
+
- `integrations/git/__tests__/runPlatformGateEvaluation.test.ts`
|
|
94
|
+
- Validación:
|
|
95
|
+
- `npm run -s typecheck`
|
|
96
|
+
- `npm run -s test:stage-gates`
|
|
@@ -28,6 +28,7 @@ type CustomSkillsRuleV1 = {
|
|
|
28
28
|
confidence?: SkillsRuleConfidence;
|
|
29
29
|
locked?: boolean;
|
|
30
30
|
evaluationMode?: SkillsRuleEvaluationMode;
|
|
31
|
+
ast_node_ids?: string[];
|
|
31
32
|
};
|
|
32
33
|
|
|
33
34
|
export type CustomSkillsRulesFileV1 = {
|
|
@@ -113,6 +114,14 @@ const isCustomRule = (value: unknown): value is CustomSkillsRuleV1 => {
|
|
|
113
114
|
) {
|
|
114
115
|
return false;
|
|
115
116
|
}
|
|
117
|
+
if (
|
|
118
|
+
typeof value.ast_node_ids !== 'undefined' &&
|
|
119
|
+
(!Array.isArray(value.ast_node_ids) ||
|
|
120
|
+
value.ast_node_ids.length === 0 ||
|
|
121
|
+
!value.ast_node_ids.every(isNonEmptyString))
|
|
122
|
+
) {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
116
125
|
if (typeof value.locked !== 'undefined' && typeof value.locked !== 'boolean') {
|
|
117
126
|
return false;
|
|
118
127
|
}
|
|
@@ -196,6 +205,10 @@ const toCustomRulesFile = (
|
|
|
196
205
|
confidence: rule.confidence,
|
|
197
206
|
locked: rule.locked ?? false,
|
|
198
207
|
evaluationMode: rule.evaluationMode ?? 'AUTO',
|
|
208
|
+
ast_node_ids:
|
|
209
|
+
rule.astNodeIds && rule.astNodeIds.length > 0
|
|
210
|
+
? [...new Set(rule.astNodeIds)].sort()
|
|
211
|
+
: undefined,
|
|
199
212
|
}))
|
|
200
213
|
.sort((left, right) => left.id.localeCompare(right.id));
|
|
201
214
|
|
|
@@ -239,6 +252,7 @@ export const loadCustomSkillsLock = (
|
|
|
239
252
|
|
|
240
253
|
const rules: SkillsCompiledRule[] = payload.rules.map((rule) => ({
|
|
241
254
|
...rule,
|
|
255
|
+
astNodeIds: rule.ast_node_ids,
|
|
242
256
|
sourceSkill: 'custom-guidelines',
|
|
243
257
|
sourcePath: '.pumuki/custom-rules.json',
|
|
244
258
|
evaluationMode: rule.evaluationMode ?? 'AUTO',
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { normalizeSkillsAstNodeIds, type SkillsCompiledRule } from './skillsLock';
|
|
2
2
|
|
|
3
3
|
type SkillsDetectorKind = 'heuristic';
|
|
4
4
|
|
|
@@ -153,3 +153,13 @@ export const resolveMappedHeuristicRuleIds = (
|
|
|
153
153
|
): ReadonlyArray<string> => {
|
|
154
154
|
return registryByRuleId[ruleId]?.mappedHeuristicRuleIds ?? [];
|
|
155
155
|
};
|
|
156
|
+
|
|
157
|
+
export const resolveMappedHeuristicRuleIdsForCompiledRule = (
|
|
158
|
+
rule: SkillsCompiledRule
|
|
159
|
+
): ReadonlyArray<string> => {
|
|
160
|
+
const dynamicAstNodeIds = normalizeSkillsAstNodeIds(rule.astNodeIds);
|
|
161
|
+
if (dynamicAstNodeIds.length > 0) {
|
|
162
|
+
return dynamicAstNodeIds;
|
|
163
|
+
}
|
|
164
|
+
return resolveMappedHeuristicRuleIds(rule.id);
|
|
165
|
+
};
|
|
@@ -10,6 +10,7 @@ export type SkillsStage = Exclude<GateStage, 'STAGED'>;
|
|
|
10
10
|
export type SkillsRuleConfidence = 'HIGH' | 'MEDIUM' | 'LOW';
|
|
11
11
|
export type SkillsRuleEvaluationMode = 'AUTO' | 'DECLARATIVE';
|
|
12
12
|
export type SkillsRuleOrigin = 'core' | 'custom';
|
|
13
|
+
export type SkillsAstNodeId = string;
|
|
13
14
|
|
|
14
15
|
export type SkillsCompiledRule = {
|
|
15
16
|
id: string;
|
|
@@ -23,6 +24,7 @@ export type SkillsCompiledRule = {
|
|
|
23
24
|
locked?: boolean;
|
|
24
25
|
evaluationMode?: SkillsRuleEvaluationMode;
|
|
25
26
|
origin?: SkillsRuleOrigin;
|
|
27
|
+
astNodeIds?: ReadonlyArray<SkillsAstNodeId>;
|
|
26
28
|
};
|
|
27
29
|
|
|
28
30
|
export type SkillsLockBundle = {
|
|
@@ -43,6 +45,7 @@ export type SkillsLockV1 = {
|
|
|
43
45
|
const SKILLS_LOCK_FILE = 'skills.lock.json';
|
|
44
46
|
const SEMVER_PATTERN = /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/;
|
|
45
47
|
const SHA256_PATTERN = /^[A-Fa-f0-9]{64}$/;
|
|
48
|
+
const AST_NODE_ID_PATTERN = /^heuristics\.[A-Za-z0-9._-]+\.ast$/;
|
|
46
49
|
|
|
47
50
|
const severityValues = new Set<Severity>(['INFO', 'WARN', 'ERROR', 'CRITICAL']);
|
|
48
51
|
const stageValues = new Set<SkillsStage>(['PRE_COMMIT', 'PRE_PUSH', 'CI']);
|
|
@@ -90,6 +93,23 @@ const isRuleEvaluationMode = (value: unknown): value is SkillsRuleEvaluationMode
|
|
|
90
93
|
return typeof value === 'string' && evaluationModeValues.has(value as SkillsRuleEvaluationMode);
|
|
91
94
|
};
|
|
92
95
|
|
|
96
|
+
const isAstNodeId = (value: unknown): value is SkillsAstNodeId => {
|
|
97
|
+
return typeof value === 'string' && AST_NODE_ID_PATTERN.test(value.trim());
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export const normalizeSkillsAstNodeIds = (
|
|
101
|
+
astNodeIds?: ReadonlyArray<SkillsAstNodeId>
|
|
102
|
+
): SkillsAstNodeId[] => {
|
|
103
|
+
if (!astNodeIds || astNodeIds.length === 0) {
|
|
104
|
+
return [];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const normalized = astNodeIds
|
|
108
|
+
.map((value) => value.trim().toLowerCase())
|
|
109
|
+
.filter((value) => AST_NODE_ID_PATTERN.test(value));
|
|
110
|
+
return [...new Set(normalized)].sort();
|
|
111
|
+
};
|
|
112
|
+
|
|
93
113
|
const isRuleOrigin = (value: unknown): value is SkillsRuleOrigin => {
|
|
94
114
|
return typeof value === 'string' && originValues.has(value as SkillsRuleOrigin);
|
|
95
115
|
};
|
|
@@ -146,6 +166,15 @@ const isSkillsCompiledRule = (value: unknown): value is SkillsCompiledRule => {
|
|
|
146
166
|
return false;
|
|
147
167
|
}
|
|
148
168
|
|
|
169
|
+
if (
|
|
170
|
+
typeof value.astNodeIds !== 'undefined' &&
|
|
171
|
+
(!Array.isArray(value.astNodeIds) ||
|
|
172
|
+
value.astNodeIds.length === 0 ||
|
|
173
|
+
value.astNodeIds.some((item) => !isAstNodeId(item)))
|
|
174
|
+
) {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
|
|
149
178
|
if (typeof value.origin !== 'undefined' && !isRuleOrigin(value.origin)) {
|
|
150
179
|
return false;
|
|
151
180
|
}
|
|
@@ -221,6 +250,7 @@ const normalizedRuleForHash = (rule: SkillsCompiledRule): Record<string, string
|
|
|
221
250
|
locked: rule.locked ?? false,
|
|
222
251
|
evaluationMode: rule.evaluationMode ?? null,
|
|
223
252
|
origin: rule.origin ?? null,
|
|
253
|
+
astNodeIds: normalizeSkillsAstNodeIds(rule.astNodeIds),
|
|
224
254
|
};
|
|
225
255
|
};
|
|
226
256
|
|
|
@@ -13,6 +13,7 @@ const MARKDOWN_LINK_PATTERN = /\[([^\]]+)\]\(([^)]+)\)/g;
|
|
|
13
13
|
const INLINE_CODE_PATTERN = /`([^`]+)`/g;
|
|
14
14
|
const MARKDOWN_BOLD_PATTERN = /[*_]{1,3}/g;
|
|
15
15
|
const MULTISPACE_PATTERN = /\s+/g;
|
|
16
|
+
const AST_NODE_ID_PATTERN = /\bheuristics\.[a-z0-9._-]+\.ast\b/gi;
|
|
16
17
|
const RULE_KEYWORDS =
|
|
17
18
|
/\b(always|siempre|prefer|use|usar|avoid|evitar|never|nunca|must|obligatorio|required|disallow|do not|no)\b/i;
|
|
18
19
|
|
|
@@ -132,6 +133,15 @@ const extractRuleCandidateLines = (markdown: string): string[] => {
|
|
|
132
133
|
return candidates;
|
|
133
134
|
};
|
|
134
135
|
|
|
136
|
+
const extractAstNodeIdsFromLine = (line: string): string[] => {
|
|
137
|
+
const matches = line.match(AST_NODE_ID_PATTERN);
|
|
138
|
+
if (!matches) {
|
|
139
|
+
return [];
|
|
140
|
+
}
|
|
141
|
+
const normalized = matches.map((token) => token.trim().toLowerCase());
|
|
142
|
+
return [...new Set(normalized)].sort();
|
|
143
|
+
};
|
|
144
|
+
|
|
135
145
|
const resolvePlatformFromBundle = (
|
|
136
146
|
bundleName: string
|
|
137
147
|
): SkillsCompiledRule['platform'] => {
|
|
@@ -301,6 +311,7 @@ export const extractCompiledRulesFromSkillMarkdown = (params: {
|
|
|
301
311
|
if (description.length < 6) {
|
|
302
312
|
continue;
|
|
303
313
|
}
|
|
314
|
+
const astNodeIds = extractAstNodeIdsFromLine(rawLine);
|
|
304
315
|
|
|
305
316
|
const knownRuleId = normalizeKnownRuleTarget(platform, normalizeForLookup(description));
|
|
306
317
|
let nextId: string;
|
|
@@ -325,9 +336,8 @@ export const extractCompiledRulesFromSkillMarkdown = (params: {
|
|
|
325
336
|
}
|
|
326
337
|
usedIds.add(nextId);
|
|
327
338
|
|
|
328
|
-
const evaluationMode: SkillsRuleEvaluationMode =
|
|
329
|
-
? 'AUTO'
|
|
330
|
-
: 'DECLARATIVE';
|
|
339
|
+
const evaluationMode: SkillsRuleEvaluationMode =
|
|
340
|
+
knownRuleId || astNodeIds.length > 0 ? 'AUTO' : 'DECLARATIVE';
|
|
331
341
|
|
|
332
342
|
rules.push({
|
|
333
343
|
id: nextId,
|
|
@@ -341,6 +351,7 @@ export const extractCompiledRulesFromSkillMarkdown = (params: {
|
|
|
341
351
|
locked: true,
|
|
342
352
|
evaluationMode,
|
|
343
353
|
origin: params.origin ?? 'core',
|
|
354
|
+
...(astNodeIds.length > 0 ? { astNodeIds } : {}),
|
|
344
355
|
});
|
|
345
356
|
}
|
|
346
357
|
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
import { loadSkillsPolicy, type SkillsBundlePolicy } from './skillsPolicy';
|
|
14
14
|
import type { DetectedPlatforms } from '../platform/detectPlatforms';
|
|
15
15
|
import { loadEffectiveSkillsLock } from './skillsEffectiveLock';
|
|
16
|
-
import {
|
|
16
|
+
import { resolveMappedHeuristicRuleIdsForCompiledRule } from './skillsDetectorRegistry';
|
|
17
17
|
|
|
18
18
|
export type SkillsRuleSetLoadResult = {
|
|
19
19
|
rules: RuleSet;
|
|
@@ -240,6 +240,22 @@ const toCode = (ruleId: string): string => {
|
|
|
240
240
|
return `SKILLS_${ruleId.replace(/[^A-Za-z0-9]+/g, '_').toUpperCase()}`;
|
|
241
241
|
};
|
|
242
242
|
|
|
243
|
+
const toSkillsRuntimeIrSource = (params: {
|
|
244
|
+
rule: SkillsCompiledRule;
|
|
245
|
+
mappedHeuristicRuleIds: ReadonlyArray<string>;
|
|
246
|
+
}): string => {
|
|
247
|
+
const astNodeIds = [...params.mappedHeuristicRuleIds].sort();
|
|
248
|
+
const astNodeToken = astNodeIds.length > 0 ? astNodeIds.join(',') : 'none';
|
|
249
|
+
const evaluationMode = resolveRuleEvaluationMode(params.rule);
|
|
250
|
+
return (
|
|
251
|
+
`skills-ir:rule=${params.rule.id};` +
|
|
252
|
+
`source_skill=${params.rule.sourceSkill};` +
|
|
253
|
+
`source_path=${params.rule.sourcePath};` +
|
|
254
|
+
`evaluation_mode=${evaluationMode};` +
|
|
255
|
+
`ast_nodes=[${astNodeToken}]`
|
|
256
|
+
);
|
|
257
|
+
};
|
|
258
|
+
|
|
243
259
|
const stageApplies = (
|
|
244
260
|
currentStage: Exclude<GateStage, 'STAGED'>,
|
|
245
261
|
ruleStage?: Exclude<GateStage, 'STAGED'>
|
|
@@ -333,7 +349,7 @@ const toRuleDefinition = (params: {
|
|
|
333
349
|
repoRoot: string;
|
|
334
350
|
observedFilePaths?: ReadonlyArray<string>;
|
|
335
351
|
}): RuleDefinition | undefined => {
|
|
336
|
-
const mappedHeuristicRuleIds =
|
|
352
|
+
const mappedHeuristicRuleIds = resolveMappedHeuristicRuleIdsForCompiledRule(params.rule);
|
|
337
353
|
|
|
338
354
|
if (!stageApplies(params.stage, params.rule.stage)) {
|
|
339
355
|
return undefined;
|
|
@@ -345,6 +361,10 @@ const toRuleDefinition = (params: {
|
|
|
345
361
|
bundlePolicy: params.bundlePolicy,
|
|
346
362
|
stage: params.stage,
|
|
347
363
|
});
|
|
364
|
+
const runtimeIrSource = toSkillsRuntimeIrSource({
|
|
365
|
+
rule: params.rule,
|
|
366
|
+
mappedHeuristicRuleIds,
|
|
367
|
+
});
|
|
348
368
|
|
|
349
369
|
if (evaluationMode === 'AUTO') {
|
|
350
370
|
if (mappedHeuristicRuleIds.length === 0) {
|
|
@@ -377,6 +397,7 @@ const toRuleDefinition = (params: {
|
|
|
377
397
|
kind: 'Finding',
|
|
378
398
|
message: params.rule.description,
|
|
379
399
|
code: toCode(params.rule.id),
|
|
400
|
+
source: runtimeIrSource,
|
|
380
401
|
},
|
|
381
402
|
scope: resolveScopeForPlatform(
|
|
382
403
|
params.rule.platform,
|
|
@@ -403,6 +424,7 @@ const toRuleDefinition = (params: {
|
|
|
403
424
|
kind: 'Finding',
|
|
404
425
|
message: `[Declarative] ${params.rule.description}`,
|
|
405
426
|
code: `${toCode(params.rule.id)}_DECLARATIVE`,
|
|
427
|
+
source: runtimeIrSource,
|
|
406
428
|
},
|
|
407
429
|
scope: resolveScopeForPlatform(
|
|
408
430
|
params.rule.platform,
|
|
@@ -500,7 +522,7 @@ export const loadSkillsRuleSetForStage = (
|
|
|
500
522
|
continue;
|
|
501
523
|
}
|
|
502
524
|
|
|
503
|
-
const mappedRuleIds =
|
|
525
|
+
const mappedRuleIds = resolveMappedHeuristicRuleIdsForCompiledRule(compiledRule);
|
|
504
526
|
const evaluationMode = resolveRuleEvaluationMode(compiledRule);
|
|
505
527
|
if (evaluationMode === 'AUTO' && mappedRuleIds.length === 0) {
|
|
506
528
|
unsupportedAutoRuleIds.add(compiledRule.id);
|
|
@@ -3,6 +3,7 @@ import { writeFileSync } from 'node:fs';
|
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import test from 'node:test';
|
|
5
5
|
import { withTempDir } from '../__tests__/helpers/tempDir';
|
|
6
|
+
import { getCurrentPumukiVersion } from '../lifecycle/packageInfo';
|
|
6
7
|
import type { AiEvidenceV2_1 } from './schema';
|
|
7
8
|
import { readEvidence, readEvidenceResult } from './readEvidence';
|
|
8
9
|
import { buildEvidenceChain } from './evidenceChain';
|
|
@@ -49,8 +50,8 @@ const sampleEvidence = (): AiEvidenceV2_1 => {
|
|
|
49
50
|
},
|
|
50
51
|
lifecycle: {
|
|
51
52
|
installed: true,
|
|
52
|
-
package_version:
|
|
53
|
-
lifecycle_version:
|
|
53
|
+
package_version: getCurrentPumukiVersion(),
|
|
54
|
+
lifecycle_version: getCurrentPumukiVersion(),
|
|
54
55
|
hooks: {
|
|
55
56
|
pre_commit: 'managed',
|
|
56
57
|
pre_push: 'managed',
|
|
@@ -25,8 +25,15 @@ export type EvidenceReadResult =
|
|
|
25
25
|
const toDigest = (value: string): string =>
|
|
26
26
|
`sha256:${createHash('sha256').update(value, 'utf8').digest('hex')}`;
|
|
27
27
|
|
|
28
|
-
export const readEvidenceResult = (
|
|
29
|
-
|
|
28
|
+
export const readEvidenceResult = (
|
|
29
|
+
repoRoot: string,
|
|
30
|
+
options?: { evidencePath?: string }
|
|
31
|
+
): EvidenceReadResult => {
|
|
32
|
+
const evidencePathInput = options?.evidencePath?.trim();
|
|
33
|
+
const evidencePath =
|
|
34
|
+
typeof evidencePathInput === 'string' && evidencePathInput.length > 0
|
|
35
|
+
? resolve(repoRoot, evidencePathInput)
|
|
36
|
+
: resolve(repoRoot, '.ai_evidence.json');
|
|
30
37
|
if (!existsSync(evidencePath)) {
|
|
31
38
|
return {
|
|
32
39
|
kind: 'missing',
|
|
@@ -126,7 +133,10 @@ export const readEvidenceResult = (repoRoot: string): EvidenceReadResult => {
|
|
|
126
133
|
}
|
|
127
134
|
};
|
|
128
135
|
|
|
129
|
-
export const readEvidence = (
|
|
130
|
-
|
|
136
|
+
export const readEvidence = (
|
|
137
|
+
repoRoot: string,
|
|
138
|
+
options?: { evidencePath?: string }
|
|
139
|
+
): AiEvidenceV2_1 | undefined => {
|
|
140
|
+
const result = readEvidenceResult(repoRoot, options);
|
|
131
141
|
return result.kind === 'valid' ? result.evidence : undefined;
|
|
132
142
|
};
|
|
@@ -2,6 +2,7 @@ import { execFileSync as runBinarySync } from 'node:child_process';
|
|
|
2
2
|
import { existsSync, readFileSync } from 'node:fs';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { readLifecycleStatus } from '../lifecycle/status';
|
|
5
|
+
import { resolvePumukiVersionMetadata } from '../lifecycle/packageInfo';
|
|
5
6
|
import type { RepoHardModeState, RepoHookState, RepoState } from './schema';
|
|
6
7
|
|
|
7
8
|
type HookStateShape = { exists: boolean; managedBlockPresent: boolean };
|
|
@@ -129,6 +130,9 @@ export const captureRepoState = (repoRoot: string): RepoState => {
|
|
|
129
130
|
const unstaged = statusLines.filter((line) => line[1] && line[1] !== ' ').length;
|
|
130
131
|
const { ahead, behind } = toAheadBehind(repoRoot, upstream);
|
|
131
132
|
const lifecycle = readLifecycleStatusSafe(repoRoot);
|
|
133
|
+
const versionMetadata = resolvePumukiVersionMetadata({ repoRoot });
|
|
134
|
+
const consumerFacingVersion = versionMetadata.resolvedVersion;
|
|
135
|
+
const installedVersion = versionMetadata.consumerInstalledVersion;
|
|
132
136
|
const hardModeState = readHardModeState(repoRoot);
|
|
133
137
|
|
|
134
138
|
return {
|
|
@@ -145,8 +149,12 @@ export const captureRepoState = (repoRoot: string): RepoState => {
|
|
|
145
149
|
},
|
|
146
150
|
lifecycle: {
|
|
147
151
|
installed: lifecycle.lifecycleState.installed === 'true',
|
|
148
|
-
|
|
149
|
-
|
|
152
|
+
// package/lifecycle version should be stable from consumer perspective.
|
|
153
|
+
package_version: consumerFacingVersion,
|
|
154
|
+
lifecycle_version: consumerFacingVersion,
|
|
155
|
+
package_version_source: versionMetadata.source,
|
|
156
|
+
package_version_runtime: versionMetadata.runtimeVersion,
|
|
157
|
+
package_version_installed: installedVersion,
|
|
150
158
|
hooks: {
|
|
151
159
|
pre_commit: toHookState(lifecycle.hookStatus['pre-commit']),
|
|
152
160
|
pre_push: toHookState(lifecycle.hookStatus['pre-push']),
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
2
|
import test from 'node:test';
|
|
3
|
+
import { getCurrentPumukiVersion } from '../lifecycle/packageInfo';
|
|
3
4
|
import type { AiEvidenceV2_1, CompatibilityViolation, HumanIntentState, SnapshotFinding } from './schema';
|
|
4
5
|
|
|
5
6
|
const sampleIntent = (): HumanIntentState => ({
|
|
@@ -122,8 +123,8 @@ test('AiEvidenceV2_1 soporta snapshot/ledger/platforms/rulesets con contrato 2.1
|
|
|
122
123
|
},
|
|
123
124
|
lifecycle: {
|
|
124
125
|
installed: true,
|
|
125
|
-
package_version:
|
|
126
|
-
lifecycle_version:
|
|
126
|
+
package_version: getCurrentPumukiVersion(),
|
|
127
|
+
lifecycle_version: getCurrentPumukiVersion(),
|
|
127
128
|
hooks: {
|
|
128
129
|
pre_commit: 'managed',
|
|
129
130
|
pre_push: 'managed',
|
|
@@ -169,6 +169,9 @@ export type RepoState = {
|
|
|
169
169
|
installed: boolean;
|
|
170
170
|
package_version: string | null;
|
|
171
171
|
lifecycle_version: string | null;
|
|
172
|
+
package_version_source?: 'consumer-node-modules' | 'runtime-package';
|
|
173
|
+
package_version_runtime?: string | null;
|
|
174
|
+
package_version_installed?: string | null;
|
|
172
175
|
hooks: {
|
|
173
176
|
pre_commit: RepoHookState;
|
|
174
177
|
pre_push: RepoHookState;
|
|
@@ -4,6 +4,7 @@ import { existsSync, mkdirSync, readdirSync, readFileSync } from 'node:fs';
|
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import test from 'node:test';
|
|
6
6
|
import { withTempDir } from '../__tests__/helpers/tempDir';
|
|
7
|
+
import { getCurrentPumukiVersion } from '../lifecycle/packageInfo';
|
|
7
8
|
import type { AiEvidenceV2_1 } from './schema';
|
|
8
9
|
import { writeEvidence } from './writeEvidence';
|
|
9
10
|
|
|
@@ -135,8 +136,8 @@ const sampleEvidence = (repoRoot: string): AiEvidenceV2_1 => ({
|
|
|
135
136
|
},
|
|
136
137
|
lifecycle: {
|
|
137
138
|
installed: true,
|
|
138
|
-
package_version:
|
|
139
|
-
lifecycle_version:
|
|
139
|
+
package_version: getCurrentPumukiVersion(),
|
|
140
|
+
lifecycle_version: getCurrentPumukiVersion(),
|
|
140
141
|
hooks: {
|
|
141
142
|
pre_commit: 'managed',
|
|
142
143
|
pre_push: 'managed',
|