pumuki 6.3.39 → 6.3.40

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.
Files changed (63) hide show
  1. package/README.md +21 -12
  2. package/VERSION +1 -1
  3. package/core/gate/evaluateRules.test.ts +40 -0
  4. package/core/gate/evaluateRules.ts +7 -1
  5. package/core/rules/Consequence.ts +1 -0
  6. package/docs/CONFIGURATION.md +50 -0
  7. package/docs/INSTALLATION.md +38 -11
  8. package/docs/MCP_SERVERS.md +1 -1
  9. package/docs/README.md +1 -0
  10. package/docs/RELEASE_NOTES.md +44 -0
  11. package/docs/USAGE.md +191 -9
  12. package/docs/registro-maestro-de-seguimiento.md +2 -2
  13. package/docs/seguimiento-activo-pumuki-saas-supermercados.md +1592 -1
  14. package/docs/validation/README.md +2 -1
  15. package/docs/validation/ast-intelligence-roadmap.md +96 -0
  16. package/integrations/config/skillsCustomRules.ts +14 -0
  17. package/integrations/config/skillsDetectorRegistry.ts +11 -1
  18. package/integrations/config/skillsLock.ts +30 -0
  19. package/integrations/config/skillsMarkdownRules.ts +14 -3
  20. package/integrations/config/skillsRuleSet.ts +25 -3
  21. package/integrations/evidence/readEvidence.test.ts +3 -2
  22. package/integrations/evidence/readEvidence.ts +14 -4
  23. package/integrations/evidence/repoState.ts +10 -2
  24. package/integrations/evidence/schema.test.ts +3 -2
  25. package/integrations/evidence/schema.ts +3 -0
  26. package/integrations/evidence/writeEvidence.test.ts +3 -2
  27. package/integrations/gate/evaluateAiGate.ts +511 -2
  28. package/integrations/git/GitService.ts +5 -1
  29. package/integrations/git/astIntelligenceDualValidation.ts +275 -0
  30. package/integrations/git/gitAtomicity.ts +42 -9
  31. package/integrations/git/resolveGitRefs.ts +37 -0
  32. package/integrations/git/runPlatformGate.ts +228 -1
  33. package/integrations/git/runPlatformGateEvaluation.ts +4 -0
  34. package/integrations/git/stageRunners.ts +116 -2
  35. package/integrations/lifecycle/cli.ts +759 -22
  36. package/integrations/lifecycle/doctor.ts +62 -0
  37. package/integrations/lifecycle/index.ts +1 -0
  38. package/integrations/lifecycle/packageInfo.ts +25 -3
  39. package/integrations/lifecycle/policyReconcile.ts +304 -0
  40. package/integrations/lifecycle/preWriteAutomation.ts +42 -2
  41. package/integrations/lifecycle/watch.ts +365 -0
  42. package/integrations/mcp/aiGateCheck.ts +59 -2
  43. package/integrations/mcp/autoExecuteAiStart.ts +25 -1
  44. package/integrations/mcp/preFlightCheck.ts +13 -0
  45. package/integrations/sdd/evidenceScaffold.ts +223 -0
  46. package/integrations/sdd/index.ts +2 -0
  47. package/integrations/sdd/stateSync.ts +400 -0
  48. package/integrations/sdd/syncDocs.ts +97 -2
  49. package/package.json +4 -1
  50. package/scripts/backlog-action-reasons-lib.ts +38 -0
  51. package/scripts/backlog-id-issue-map-lib.ts +69 -0
  52. package/scripts/backlog-json-contract-lib.ts +3 -0
  53. package/scripts/framework-menu-consumer-preflight-lib.ts +6 -0
  54. package/scripts/package-install-smoke-command-resolution-lib.ts +64 -0
  55. package/scripts/package-install-smoke-consumer-npm-lib.ts +43 -0
  56. package/scripts/package-install-smoke-consumer-repo-setup-lib.ts +2 -0
  57. package/scripts/package-install-smoke-execution-steps-lib.ts +27 -9
  58. package/scripts/package-install-smoke-lifecycle-lib.ts +15 -4
  59. package/scripts/package-install-smoke-workspace-factory-lib.ts +4 -1
  60. package/scripts/reconcile-consumer-backlog-issues-lib.ts +651 -0
  61. package/scripts/reconcile-consumer-backlog-issues.ts +348 -0
  62. package/scripts/watch-consumer-backlog-lib.ts +465 -0
  63. package/scripts/watch-consumer-backlog.ts +326 -0
@@ -0,0 +1,275 @@
1
+ import type { Fact } from '../../core/facts/Fact';
2
+ import type { Finding } from '../../core/gate/Finding';
3
+ import type { RuleSet } from '../../core/rules/RuleSet';
4
+
5
+ export type AstIntelligenceDualValidationMode = 'off' | 'shadow' | 'strict';
6
+
7
+ export type AstIntelligenceDualValidationSummary = {
8
+ mapped_rules: number;
9
+ compared_rules: number;
10
+ legacy_triggered: number;
11
+ ast_triggered: number;
12
+ divergences: number;
13
+ false_positives: number;
14
+ false_negatives: number;
15
+ latency_ms: number;
16
+ languages: ReadonlyArray<string>;
17
+ sample_divergence_rule_ids: ReadonlyArray<string>;
18
+ };
19
+
20
+ export type AstIntelligenceDualValidationResult = {
21
+ mode: AstIntelligenceDualValidationMode;
22
+ summary: AstIntelligenceDualValidationSummary;
23
+ finding?: Finding;
24
+ };
25
+
26
+ type ComparableMappedRule = {
27
+ ruleId: string;
28
+ astNodeRuleIds: ReadonlyArray<string>;
29
+ };
30
+
31
+ const AST_INTELLIGENCE_DUAL_MODE_ENV = 'PUMUKI_AST_INTELLIGENCE_DUAL_MODE';
32
+ const SKILLS_IR_PREFIX = 'skills-ir:';
33
+ const AST_NODES_TOKEN_PATTERN = /(?:^|;)ast_nodes=\[([^\]]*)\]/;
34
+
35
+ const EMPTY_SUMMARY: AstIntelligenceDualValidationSummary = {
36
+ mapped_rules: 0,
37
+ compared_rules: 0,
38
+ legacy_triggered: 0,
39
+ ast_triggered: 0,
40
+ divergences: 0,
41
+ false_positives: 0,
42
+ false_negatives: 0,
43
+ latency_ms: 0,
44
+ languages: [],
45
+ sample_divergence_rule_ids: [],
46
+ };
47
+
48
+ const isString = (value: unknown): value is string =>
49
+ typeof value === 'string' && value.trim().length > 0;
50
+
51
+ const toNormalizedPath = (path: string): string => path.replace(/\\/g, '/').trim();
52
+
53
+ const toLanguageTokenFromPath = (path: string): string | null => {
54
+ const normalized = toNormalizedPath(path).toLowerCase();
55
+ if (
56
+ normalized.endsWith('.ts')
57
+ || normalized.endsWith('.tsx')
58
+ || normalized.endsWith('.js')
59
+ || normalized.endsWith('.jsx')
60
+ || normalized.endsWith('.mts')
61
+ || normalized.endsWith('.cts')
62
+ || normalized.endsWith('.mjs')
63
+ || normalized.endsWith('.cjs')
64
+ ) {
65
+ return 'typescript';
66
+ }
67
+ if (normalized.endsWith('.swift')) {
68
+ return 'swift';
69
+ }
70
+ if (normalized.endsWith('.kt') || normalized.endsWith('.kts')) {
71
+ return 'kotlin';
72
+ }
73
+ return null;
74
+ };
75
+
76
+ const detectLanguagesFromFacts = (facts: ReadonlyArray<Fact>): ReadonlyArray<string> => {
77
+ const languages = new Set<string>();
78
+ for (const fact of facts) {
79
+ if (fact.kind !== 'FileChange' && fact.kind !== 'FileContent' && fact.kind !== 'Heuristic') {
80
+ continue;
81
+ }
82
+ const rawPath = fact.kind === 'Heuristic' ? fact.filePath : fact.path;
83
+ if (!isString(rawPath)) {
84
+ continue;
85
+ }
86
+ const language = toLanguageTokenFromPath(rawPath);
87
+ if (language) {
88
+ languages.add(language);
89
+ }
90
+ }
91
+ return [...languages].sort();
92
+ };
93
+
94
+ const parseAstNodesFromSource = (source: string | undefined): ReadonlyArray<string> => {
95
+ if (!isString(source) || !source.startsWith(SKILLS_IR_PREFIX)) {
96
+ return [];
97
+ }
98
+ const match = source.match(AST_NODES_TOKEN_PATTERN);
99
+ if (!match || !isString(match[1])) {
100
+ return [];
101
+ }
102
+ const astNodeRuleIds = match[1]
103
+ .split(',')
104
+ .map((token) => token.trim())
105
+ .filter((token) => token.length > 0 && token !== 'none');
106
+ return [...new Set(astNodeRuleIds)].sort();
107
+ };
108
+
109
+ const collectComparableMappedRules = (
110
+ skillsRules: RuleSet
111
+ ): ReadonlyArray<ComparableMappedRule> => {
112
+ return skillsRules
113
+ .filter(
114
+ (rule) =>
115
+ rule.id.startsWith('skills.')
116
+ && rule.then.kind === 'Finding'
117
+ && parseAstNodesFromSource(rule.then.source).length > 0
118
+ )
119
+ .map((rule) => ({
120
+ ruleId: rule.id,
121
+ astNodeRuleIds: parseAstNodesFromSource(rule.then.source),
122
+ }))
123
+ .sort((left, right) => left.ruleId.localeCompare(right.ruleId));
124
+ };
125
+
126
+ const collectHeuristicRuleIds = (facts: ReadonlyArray<Fact>): ReadonlySet<string> => {
127
+ const heuristicRuleIds = new Set<string>();
128
+ for (const fact of facts) {
129
+ if (fact.kind === 'Heuristic' && isString(fact.ruleId)) {
130
+ heuristicRuleIds.add(fact.ruleId.trim());
131
+ }
132
+ }
133
+ return heuristicRuleIds;
134
+ };
135
+
136
+ const collectLegacyTriggeredRuleIds = (
137
+ legacyFindings: ReadonlyArray<Finding>
138
+ ): ReadonlySet<string> => {
139
+ const legacyRuleIds = new Set<string>();
140
+ for (const finding of legacyFindings) {
141
+ if (isString(finding.ruleId)) {
142
+ legacyRuleIds.add(finding.ruleId.trim());
143
+ }
144
+ }
145
+ return legacyRuleIds;
146
+ };
147
+
148
+ export const resolveAstIntelligenceDualValidationMode = (
149
+ explicitMode?: string
150
+ ): AstIntelligenceDualValidationMode => {
151
+ const raw = (explicitMode ?? process.env[AST_INTELLIGENCE_DUAL_MODE_ENV] ?? 'off')
152
+ .trim()
153
+ .toLowerCase();
154
+ if (raw === 'strict' || raw === 'enforce' || raw === 'block') {
155
+ return 'strict';
156
+ }
157
+ if (raw === 'shadow' || raw === 'dual' || raw === '1' || raw === 'true' || raw === 'on') {
158
+ return 'shadow';
159
+ }
160
+ return 'off';
161
+ };
162
+
163
+ const toDualValidationFinding = (params: {
164
+ mode: Exclude<AstIntelligenceDualValidationMode, 'off'>;
165
+ stage: 'PRE_COMMIT' | 'PRE_PUSH' | 'CI';
166
+ summary: AstIntelligenceDualValidationSummary;
167
+ }): Finding | undefined => {
168
+ if (params.summary.divergences === 0) {
169
+ return undefined;
170
+ }
171
+ const sampleRuleIds = params.summary.sample_divergence_rule_ids.join(', ');
172
+ if (params.mode === 'strict') {
173
+ return {
174
+ ruleId: 'governance.ast-intelligence.dual-validation.mismatch',
175
+ severity: 'ERROR',
176
+ code: 'AST_INTELLIGENCE_DUAL_VALIDATION_MISMATCH_HIGH',
177
+ message:
178
+ `AST Intelligence dual validation mismatch at ${params.stage}: ` +
179
+ `divergences=${params.summary.divergences} ` +
180
+ `false_positives=${params.summary.false_positives} ` +
181
+ `false_negatives=${params.summary.false_negatives} ` +
182
+ `sample_rule_ids=[${sampleRuleIds}].`,
183
+ filePath: '.ai_evidence.json',
184
+ matchedBy: 'AstIntelligenceDualValidationGuard',
185
+ source: 'ast-intelligence-dual-mode',
186
+ };
187
+ }
188
+ return {
189
+ ruleId: 'governance.ast-intelligence.dual-validation.shadow',
190
+ severity: 'INFO',
191
+ code: 'AST_INTELLIGENCE_DUAL_VALIDATION_SHADOW',
192
+ message:
193
+ `AST Intelligence dual validation shadow at ${params.stage}: ` +
194
+ `divergences=${params.summary.divergences} ` +
195
+ `false_positives=${params.summary.false_positives} ` +
196
+ `false_negatives=${params.summary.false_negatives} ` +
197
+ `sample_rule_ids=[${sampleRuleIds}].`,
198
+ filePath: '.ai_evidence.json',
199
+ matchedBy: 'AstIntelligenceDualValidationGuard',
200
+ source: 'ast-intelligence-dual-mode',
201
+ };
202
+ };
203
+
204
+ export const evaluateAstIntelligenceDualValidation = (params: {
205
+ stage: 'PRE_COMMIT' | 'PRE_PUSH' | 'CI';
206
+ skillsRules: RuleSet;
207
+ facts: ReadonlyArray<Fact>;
208
+ legacyFindings: ReadonlyArray<Finding>;
209
+ mode?: string;
210
+ }): AstIntelligenceDualValidationResult => {
211
+ const mode = resolveAstIntelligenceDualValidationMode(params.mode);
212
+ if (mode === 'off') {
213
+ return {
214
+ mode,
215
+ summary: EMPTY_SUMMARY,
216
+ };
217
+ }
218
+ const startedAt = Date.now();
219
+ const comparableMappedRules = collectComparableMappedRules(params.skillsRules);
220
+ const heuristicRuleIds = collectHeuristicRuleIds(params.facts);
221
+ const legacyTriggeredRuleIds = collectLegacyTriggeredRuleIds(params.legacyFindings);
222
+ const languages = detectLanguagesFromFacts(params.facts);
223
+
224
+ let legacyTriggered = 0;
225
+ let astTriggered = 0;
226
+ let falsePositives = 0;
227
+ let falseNegatives = 0;
228
+ const divergenceRuleIds: string[] = [];
229
+
230
+ for (const mappedRule of comparableMappedRules) {
231
+ const isLegacyTriggered = legacyTriggeredRuleIds.has(mappedRule.ruleId);
232
+ const isAstTriggered = mappedRule.astNodeRuleIds.some((astNodeRuleId) =>
233
+ heuristicRuleIds.has(astNodeRuleId)
234
+ );
235
+
236
+ if (isLegacyTriggered) {
237
+ legacyTriggered += 1;
238
+ }
239
+ if (isAstTriggered) {
240
+ astTriggered += 1;
241
+ }
242
+ if (isLegacyTriggered === isAstTriggered) {
243
+ continue;
244
+ }
245
+ if (isAstTriggered) {
246
+ falsePositives += 1;
247
+ } else {
248
+ falseNegatives += 1;
249
+ }
250
+ divergenceRuleIds.push(mappedRule.ruleId);
251
+ }
252
+
253
+ const summary: AstIntelligenceDualValidationSummary = {
254
+ mapped_rules: comparableMappedRules.length,
255
+ compared_rules: comparableMappedRules.length,
256
+ legacy_triggered: legacyTriggered,
257
+ ast_triggered: astTriggered,
258
+ divergences: divergenceRuleIds.length,
259
+ false_positives: falsePositives,
260
+ false_negatives: falseNegatives,
261
+ latency_ms: Math.max(0, Date.now() - startedAt),
262
+ languages,
263
+ sample_divergence_rule_ids: divergenceRuleIds.slice(0, 5),
264
+ };
265
+
266
+ return {
267
+ mode,
268
+ summary,
269
+ finding: toDualValidationFinding({
270
+ mode,
271
+ stage: params.stage,
272
+ summary,
273
+ }),
274
+ };
275
+ };
@@ -138,6 +138,25 @@ const parseLines = (value: string): ReadonlyArray<string> =>
138
138
  .map((line) => line.trim())
139
139
  .filter((line) => line.length > 0);
140
140
 
141
+ const toErrorMessage = (error: unknown): string => {
142
+ if (error instanceof Error) {
143
+ return error.message;
144
+ }
145
+ return String(error);
146
+ };
147
+
148
+ const isUnresolvableRevisionError = (error: unknown): boolean => {
149
+ const message = toErrorMessage(error).toLowerCase();
150
+ return (
151
+ message.includes('unknown revision') ||
152
+ message.includes('bad revision') ||
153
+ message.includes('ambiguous argument') ||
154
+ message.includes('argumento ambiguo') ||
155
+ message.includes('invalid object name') ||
156
+ message.includes('not a valid object name')
157
+ );
158
+ };
159
+
141
160
  const resolveScopeKey = (filePath: string): string => {
142
161
  const normalized = filePath.replace(/\\/g, '/').trim();
143
162
  const segments = normalized.split('/').filter((segment) => segment.length > 0);
@@ -168,12 +187,19 @@ const collectChangedPaths = (params: {
168
187
  if (!params.fromRef || !params.toRef) {
169
188
  return [];
170
189
  }
171
- return parseLines(
172
- params.git.runGit(
173
- ['diff', '--name-only', '--diff-filter=ACMR', `${params.fromRef}..${params.toRef}`],
174
- params.repoRoot
175
- )
176
- );
190
+ try {
191
+ return parseLines(
192
+ params.git.runGit(
193
+ ['diff', '--name-only', '--diff-filter=ACMR', `${params.fromRef}..${params.toRef}`],
194
+ params.repoRoot
195
+ )
196
+ );
197
+ } catch (error) {
198
+ if (isUnresolvableRevisionError(error)) {
199
+ return [];
200
+ }
201
+ throw error;
202
+ }
177
203
  };
178
204
 
179
205
  const collectCommitSubjects = (params: {
@@ -185,9 +211,16 @@ const collectCommitSubjects = (params: {
185
211
  if (!params.fromRef || !params.toRef) {
186
212
  return [];
187
213
  }
188
- return parseLines(
189
- params.git.runGit(['log', '--format=%s', `${params.fromRef}..${params.toRef}`], params.repoRoot)
190
- );
214
+ try {
215
+ return parseLines(
216
+ params.git.runGit(['log', '--format=%s', `${params.fromRef}..${params.toRef}`], params.repoRoot)
217
+ );
218
+ } catch (error) {
219
+ if (isUnresolvableRevisionError(error)) {
220
+ return [];
221
+ }
222
+ throw error;
223
+ }
191
224
  };
192
225
 
193
226
  export const evaluateGitAtomicity = (params: {
@@ -34,6 +34,43 @@ export const resolveUpstreamRef = (): string | null => {
34
34
  }
35
35
  };
36
36
 
37
+ export const resolveCurrentBranchRef = (): string | null => {
38
+ try {
39
+ const branch = runGit(['rev-parse', '--abbrev-ref', 'HEAD']);
40
+ if (branch.length === 0 || branch === 'HEAD') {
41
+ return null;
42
+ }
43
+ return branch;
44
+ } catch {
45
+ return null;
46
+ }
47
+ };
48
+
49
+ export const resolveUpstreamTrackingRef = (): string | null => {
50
+ try {
51
+ return runGit(['rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{u}']);
52
+ } catch {
53
+ return null;
54
+ }
55
+ };
56
+
57
+ export const resolveAheadBehindFromRef = (
58
+ fromRef: string
59
+ ): { ahead: number; behind: number } | null => {
60
+ try {
61
+ const raw = runGit(['rev-list', '--left-right', '--count', `${fromRef}...HEAD`]);
62
+ const [behindRaw, aheadRaw] = raw.split(/\s+/);
63
+ const ahead = Number.parseInt(aheadRaw ?? '', 10);
64
+ const behind = Number.parseInt(behindRaw ?? '', 10);
65
+ if (!Number.isFinite(ahead) || !Number.isFinite(behind)) {
66
+ return null;
67
+ }
68
+ return { ahead, behind };
69
+ } catch {
70
+ return null;
71
+ }
72
+ };
73
+
37
74
  export const resolveCiBaseRef = (): string => {
38
75
  const fromEnv = process.env.GITHUB_BASE_REF?.trim();
39
76
  if (fromEnv) {