pumuki 6.3.44 → 6.3.45
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/docs/RELEASE_NOTES.md +18 -0
- package/docs/seguimiento-activo-pumuki-saas-supermercados.md +87 -5
- package/integrations/git/runPlatformGate.ts +30 -19
- package/integrations/lifecycle/watch.ts +1 -0
- package/integrations/mcp/aiGateCheck.ts +16 -2
- package/integrations/mcp/autoExecuteAiStart.ts +12 -2
- package/integrations/mcp/preFlightCheck.ts +16 -0
- package/integrations/sdd/learningInsights.ts +91 -0
- package/integrations/sdd/syncDocs.ts +317 -28
- package/package.json +1 -1
package/docs/RELEASE_NOTES.md
CHANGED
|
@@ -5,6 +5,24 @@ Detailed commit history remains available through Git history (`git log` / `git
|
|
|
5
5
|
|
|
6
6
|
## 2026-03 (enterprise hardening updates)
|
|
7
7
|
|
|
8
|
+
### 2026-03-05 (v6.3.45)
|
|
9
|
+
|
|
10
|
+
- SDD sync canónico ampliado por defecto en consumer:
|
|
11
|
+
- `pumuki sdd sync-docs` ahora sincroniza, cuando existen, los 3 documentos base:
|
|
12
|
+
- `docs/strategy/ruralgo-tracking-hub.md`
|
|
13
|
+
- `docs/technical/08-validation/refactor/operational-summary.md`
|
|
14
|
+
- `docs/validation/refactor/last-run.json`
|
|
15
|
+
- Auto-sync OpenSpec integral por cambio:
|
|
16
|
+
- `pumuki sdd auto-sync` incluye por defecto:
|
|
17
|
+
- `openspec/changes/<change>/tasks.md`
|
|
18
|
+
- `openspec/changes/<change>/design.md`
|
|
19
|
+
- `openspec/changes/<change>/retrospective.md`
|
|
20
|
+
- Consumo automático universal de aprendizaje:
|
|
21
|
+
- `ai_gate_check`, `pre_flight_check` y `auto_execute_ai_start` exponen `learning_context` cuando existe `openspec/changes/<change>/learning.json`.
|
|
22
|
+
- Evidencia de validación:
|
|
23
|
+
- `npx --yes tsx@4.21.0 --test integrations/mcp/__tests__/aiGateCheck.test.ts integrations/mcp/__tests__/preFlightCheck.test.ts integrations/mcp/__tests__/autoExecuteAiStart.test.ts integrations/sdd/__tests__/syncDocs.test.ts integrations/lifecycle/__tests__/cli.test.ts` (`78 pass / 0 fail`)
|
|
24
|
+
- `npm run -s typecheck` (`PASS`)
|
|
25
|
+
|
|
8
26
|
### 2026-03-05 (v6.3.43)
|
|
9
27
|
|
|
10
28
|
- `pumuki sdd evidence` alinea su salida con el contrato TDD/BDD del gate:
|
|
@@ -2099,12 +2099,94 @@
|
|
|
2099
2099
|
- `npx --yes tsx@4.21.0 --test integrations/lifecycle/__tests__/watch.test.ts integrations/lifecycle/__tests__/cli.test.ts integrations/lifecycle/__tests__/policyReconcile.test.ts` -> `53 pass / 0 fail`.
|
|
2100
2100
|
- `npm run -s typecheck` -> `PASS`.
|
|
2101
2101
|
|
|
2102
|
-
-
|
|
2102
|
+
- ✅ PUMUKI-162: Ejecutar siguiente pendiente activo de Flux (`PUM-011`) para cerrar paridad consumer de `watch --once --json` con `lastTick.changedFiles[]` y `lastTick.evaluatedFiles[]`.
|
|
2103
2103
|
- Alcance:
|
|
2104
2104
|
- verificar contrato JSON final en paquete publicado vs consumer runtime,
|
|
2105
2105
|
- eliminar drift de payload entre core y distribución npm,
|
|
2106
2106
|
- cerrar con validación reproducible en consumer (sin tocar código funcional del consumer).
|
|
2107
|
-
-
|
|
2108
|
-
-
|
|
2109
|
-
-
|
|
2110
|
-
|
|
2107
|
+
- Resultado (2026-03-05):
|
|
2108
|
+
- release publicado: `pumuki@6.3.44`.
|
|
2109
|
+
- verificación `@latest` en repo temporal:
|
|
2110
|
+
- `lastTick.changedFiles[]` presente.
|
|
2111
|
+
- `lastTick.evaluatedFiles[]` presente.
|
|
2112
|
+
- leyenda Flux actualizada:
|
|
2113
|
+
- `PUM-011` -> `✅ Cerrado`.
|
|
2114
|
+
|
|
2115
|
+
- ✅ PUMUKI-163: Ejecutar siguiente pendiente activo de Flux (`PUM-012`) para garantizar contrato JSON estricto (`stdout` solo JSON) cuando se usa `--json`.
|
|
2116
|
+
- Resultado (2026-03-05):
|
|
2117
|
+
- `integrations/git/runPlatformGate.ts` añade `silent?: boolean` para suprimir salida humana en `stdout` al ejecutar en flujos machine-readable.
|
|
2118
|
+
- `integrations/lifecycle/watch.ts` invoca `runPlatformGate(..., { silent: true })` en `watch --json` para mantener contrato estricto de parseo.
|
|
2119
|
+
- cobertura de regresión añadida en `integrations/git/__tests__/runPlatformGate.test.ts`:
|
|
2120
|
+
- `runPlatformGate silent evita salida humana en stdout para contratos JSON`.
|
|
2121
|
+
- validación funcional de contrato:
|
|
2122
|
+
- `node bin/pumuki.js watch --once --stage=PRE_COMMIT --scope=workingTree --json | jq -e '.status'` -> `JSON_PIPE_OK`.
|
|
2123
|
+
- leyenda Flux actualizada:
|
|
2124
|
+
- `PUM-012` -> `✅ Cerrado`.
|
|
2125
|
+
- backlog Flux externo queda en `✅ 100% cerrado`.
|
|
2126
|
+
- Evidencia:
|
|
2127
|
+
- `npx --yes tsx@4.21.0 --test integrations/git/__tests__/runPlatformGate.test.ts integrations/lifecycle/__tests__/watch.test.ts integrations/lifecycle/__tests__/cli.test.ts` -> `81 pass / 0 fail`.
|
|
2128
|
+
- `npm run -s typecheck` -> `PASS`.
|
|
2129
|
+
|
|
2130
|
+
- ✅ PUMUKI-164: Priorizar cierre SDD pendiente de RuralGo para `sync-docs` completo (3 docs canónicos por defecto + aprendizaje operativo) y dejar contrato enterprise sin ambigüedad.
|
|
2131
|
+
- Resultado (2026-03-05):
|
|
2132
|
+
- `integrations/sdd/syncDocs.ts` amplía targets por defecto de `sync-docs`:
|
|
2133
|
+
- `docs/strategy/ruralgo-tracking-hub.md` (sección managed auto-creable),
|
|
2134
|
+
- `docs/technical/08-validation/refactor/operational-summary.md` (sección managed auto-creable),
|
|
2135
|
+
- `docs/validation/refactor/last-run.json` (merge JSON determinista con `pumuki_sdd_status`),
|
|
2136
|
+
- manteniendo compatibilidad con target previo (`pumuki-integration-feedback.md`) y sin romper repos que no tengan esos archivos (targets opcionales por existencia).
|
|
2137
|
+
- `applyManagedSection` ahora soporta `createIfMissing` (solo cuando faltan ambos markers) para evitar conflicto falso en docs canónicos nuevos.
|
|
2138
|
+
- test de regresión añadido:
|
|
2139
|
+
- `integrations/sdd/__tests__/syncDocs.test.ts` -> `runSddSyncDocs por defecto sincroniza 3 docs canónicos SDD cuando existen en el repo consumer`.
|
|
2140
|
+
- estado RuralGo actualizado:
|
|
2141
|
+
- `/Users/juancarlosmerlosalbarracin/Developer/Projects/R_GO/docs/technical/08-validation/refactor/pumuki-integration-feedback.md`
|
|
2142
|
+
- bloque “Actualizar 3 docs canónicos del consumer por defecto” -> `✅ Implementado`.
|
|
2143
|
+
- Evidencia:
|
|
2144
|
+
- `npx --yes tsx@4.21.0 --test integrations/sdd/__tests__/syncDocs.test.ts integrations/lifecycle/__tests__/cli.test.ts` -> `59 pass / 0 fail`.
|
|
2145
|
+
- `npm run -s typecheck` -> `PASS`.
|
|
2146
|
+
|
|
2147
|
+
- ✅ PUMUKI-165: Priorizar siguiente SDD pendiente de RuralGo para auto-sync integral de artefactos OpenSpec (`tasks.md/design.md/retrospective.md`) por contrato default.
|
|
2148
|
+
- Resultado (2026-03-05):
|
|
2149
|
+
- `integrations/sdd/syncDocs.ts` amplía `runSddAutoSync` para incluir por defecto targets OpenSpec por cambio:
|
|
2150
|
+
- `openspec/changes/<change>/tasks.md`
|
|
2151
|
+
- `openspec/changes/<change>/design.md`
|
|
2152
|
+
- `openspec/changes/<change>/retrospective.md`
|
|
2153
|
+
- comportamiento idempotente y compatible:
|
|
2154
|
+
- crea archivo si no existe (`bootstrapIfMissing`),
|
|
2155
|
+
- inserta/actualiza bloque managed `AUTO_SYNC_STATUS`,
|
|
2156
|
+
- no rompe consumers existentes (targets opcionales/canónicos previos se mantienen).
|
|
2157
|
+
- cobertura actualizada:
|
|
2158
|
+
- `runSddAutoSync dry-run orquesta sync-docs + learning sin modificar archivos` ahora valida 4 archivos sincronizados (canónico + 3 OpenSpec),
|
|
2159
|
+
- `runSddAutoSync aplica sync-docs y persiste learning en modo escritura` valida creación real de `tasks/design/retrospective` con markers.
|
|
2160
|
+
- estado RuralGo actualizado:
|
|
2161
|
+
- `/Users/juancarlosmerlosalbarracin/Developer/Projects/R_GO/docs/technical/08-validation/refactor/pumuki-integration-feedback.md`
|
|
2162
|
+
- bloque “Auto-sync integral de tasks.md/design.md/retrospective.md por defecto” -> `✅ Implementado`.
|
|
2163
|
+
- Evidencia:
|
|
2164
|
+
- `npx --yes tsx@4.21.0 --test integrations/sdd/__tests__/syncDocs.test.ts integrations/lifecycle/__tests__/cli.test.ts` -> `59 pass / 0 fail`.
|
|
2165
|
+
- `npm run -s typecheck` -> `PASS`.
|
|
2166
|
+
|
|
2167
|
+
- ✅ PUMUKI-166: Priorizar último pendiente SDD de RuralGo para consumo automático universal de `learning.json` por agente/orquestador.
|
|
2168
|
+
- Resultado (2026-03-05):
|
|
2169
|
+
- nuevo helper `integrations/sdd/learningInsights.ts` para lectura robusta de `openspec/changes/<change>/learning.json` (cambio activo) y derivación de recomendaciones accionables.
|
|
2170
|
+
- integración automática en herramientas MCP:
|
|
2171
|
+
- `integrations/mcp/aiGateCheck.ts`
|
|
2172
|
+
- `integrations/mcp/preFlightCheck.ts`
|
|
2173
|
+
- `integrations/mcp/autoExecuteAiStart.ts`
|
|
2174
|
+
- salida extendida sin romper contrato:
|
|
2175
|
+
- campo `learning_context` en cada tool,
|
|
2176
|
+
- hints/auto_fixes/instruction enriquecidos con recomendaciones de learning cuando existen.
|
|
2177
|
+
- cobertura añadida:
|
|
2178
|
+
- `integrations/mcp/__tests__/aiGateCheck.test.ts` (ingesta + auto_fix learning),
|
|
2179
|
+
- `integrations/mcp/__tests__/preFlightCheck.test.ts` (learning_context en preflight),
|
|
2180
|
+
- `integrations/mcp/__tests__/autoExecuteAiStart.test.ts` (mensaje/instrucción con learning).
|
|
2181
|
+
- estado RuralGo actualizado:
|
|
2182
|
+
- `/Users/juancarlosmerlosalbarracin/Developer/Projects/R_GO/docs/technical/08-validation/refactor/pumuki-integration-feedback.md`
|
|
2183
|
+
- bloque “Consumo automático universal de learning.json por cualquier agente” -> `✅ Implementado`.
|
|
2184
|
+
- Evidencia:
|
|
2185
|
+
- `npx --yes tsx@4.21.0 --test integrations/mcp/__tests__/aiGateCheck.test.ts integrations/mcp/__tests__/preFlightCheck.test.ts integrations/mcp/__tests__/autoExecuteAiStart.test.ts integrations/sdd/__tests__/syncDocs.test.ts integrations/lifecycle/__tests__/cli.test.ts` -> `78 pass / 0 fail`.
|
|
2186
|
+
- `npm run -s typecheck` -> `PASS`.
|
|
2187
|
+
|
|
2188
|
+
- 🚧 PUMUKI-167: Preparar release + rollout consumidor tras cierre de backlog SDD/Flux/RuralGo.
|
|
2189
|
+
- Alcance:
|
|
2190
|
+
- consolidar changelog de fixes/mejoras cerradas en este bloque,
|
|
2191
|
+
- publicar nueva versión npm con evidencia de tests en verde,
|
|
2192
|
+
- ejecutar upgrade/update en repos consumidores (`SAAS`, `R_GO`) y validar smoke mínimo post-upgrade.
|
|
@@ -769,6 +769,7 @@ export async function runPlatformGate(params: {
|
|
|
769
769
|
auditMode?: 'gate' | 'engine';
|
|
770
770
|
policyTrace?: ResolvedStagePolicy['trace'];
|
|
771
771
|
scope: GateScope;
|
|
772
|
+
silent?: boolean;
|
|
772
773
|
sddShortCircuit?: boolean;
|
|
773
774
|
services?: Partial<GateServices>;
|
|
774
775
|
dependencies?: Partial<GateDependencies>;
|
|
@@ -797,7 +798,9 @@ export async function runPlatformGate(params: {
|
|
|
797
798
|
repoRoot
|
|
798
799
|
);
|
|
799
800
|
if (!sddDecision.allowed) {
|
|
800
|
-
|
|
801
|
+
if (params.silent !== true) {
|
|
802
|
+
process.stdout.write(`[pumuki][sdd] ${sddDecision.code}: ${sddDecision.message}\n`);
|
|
803
|
+
}
|
|
801
804
|
sddBlockingFinding = toSddBlockingFinding(sddDecision);
|
|
802
805
|
if (shouldShortCircuitSdd) {
|
|
803
806
|
const emptyDetectedPlatforms: DetectedPlatforms = {};
|
|
@@ -972,16 +975,18 @@ export async function runPlatformGate(params: {
|
|
|
972
975
|
const astIntelligenceDualFinding = astIntelligenceDualValidation?.finding;
|
|
973
976
|
if (astIntelligenceDualValidation && astIntelligenceDualValidation.mode !== 'off') {
|
|
974
977
|
const summary = astIntelligenceDualValidation.summary;
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
978
|
+
if (params.silent !== true) {
|
|
979
|
+
process.stdout.write(
|
|
980
|
+
`[pumuki][ast-intelligence] mode=${astIntelligenceDualValidation.mode}` +
|
|
981
|
+
` mapped_rules=${summary.mapped_rules}` +
|
|
982
|
+
` compared_rules=${summary.compared_rules}` +
|
|
983
|
+
` divergences=${summary.divergences}` +
|
|
984
|
+
` false_positives=${summary.false_positives}` +
|
|
985
|
+
` false_negatives=${summary.false_negatives}` +
|
|
986
|
+
` latency_ms=${summary.latency_ms}` +
|
|
987
|
+
` languages=[${summary.languages.join(',') || 'none'}]\n`
|
|
988
|
+
);
|
|
989
|
+
}
|
|
985
990
|
}
|
|
986
991
|
const degradedModeBlocks = params.policyTrace?.degraded?.action === 'block';
|
|
987
992
|
const rulesCoverage = coverage
|
|
@@ -1170,9 +1175,11 @@ export async function runPlatformGate(params: {
|
|
|
1170
1175
|
} catch (error) {
|
|
1171
1176
|
const rawReason = error instanceof Error ? error.message : String(error);
|
|
1172
1177
|
const reason = rawReason.trim().replace(/\s+/g, ' ');
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1178
|
+
if (params.silent !== true) {
|
|
1179
|
+
process.stdout.write(
|
|
1180
|
+
`[pumuki][memory-shadow] unavailable reason=${reason.length > 0 ? reason : 'unknown_error'}\n`
|
|
1181
|
+
);
|
|
1182
|
+
}
|
|
1176
1183
|
}
|
|
1177
1184
|
}
|
|
1178
1185
|
const memoryShadow:
|
|
@@ -1196,11 +1203,13 @@ export async function runPlatformGate(params: {
|
|
|
1196
1203
|
: undefined;
|
|
1197
1204
|
|
|
1198
1205
|
if (memoryShadowRecommendation) {
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1206
|
+
if (params.silent !== true) {
|
|
1207
|
+
process.stdout.write(
|
|
1208
|
+
`[pumuki][memory-shadow] recommended=${memoryShadowRecommendation.recommendedOutcome}` +
|
|
1209
|
+
` confidence=${memoryShadowRecommendation.confidence.toFixed(2)}` +
|
|
1210
|
+
` reasons=${memoryShadowRecommendation.reasonCodes.join(',')}\n`
|
|
1211
|
+
);
|
|
1212
|
+
}
|
|
1204
1213
|
}
|
|
1205
1214
|
|
|
1206
1215
|
dependencies.emitPlatformGateEvidence({
|
|
@@ -1224,7 +1233,9 @@ export async function runPlatformGate(params: {
|
|
|
1224
1233
|
});
|
|
1225
1234
|
|
|
1226
1235
|
if (gateOutcome === 'BLOCK') {
|
|
1236
|
+
if (params.silent !== true) {
|
|
1227
1237
|
dependencies.printGateFindings(findingsWithWaiver);
|
|
1238
|
+
}
|
|
1228
1239
|
return 1;
|
|
1229
1240
|
}
|
|
1230
1241
|
|
|
@@ -309,6 +309,7 @@ export const runLifecycleWatch = async (
|
|
|
309
309
|
policy: resolvedPolicy.policy,
|
|
310
310
|
policyTrace: resolvedPolicy.trace,
|
|
311
311
|
scope: gateScope,
|
|
312
|
+
silent: true,
|
|
312
313
|
});
|
|
313
314
|
const evidence = activeDependencies.readEvidence(repoRoot);
|
|
314
315
|
const allFindings = evidence?.snapshot.findings ?? [];
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { evaluateAiGate, type AiGateStage } from '../gate/evaluateAiGate';
|
|
2
|
+
import { readSddLearningContext, type SddLearningContext } from '../sdd/learningInsights';
|
|
2
3
|
|
|
3
4
|
const AUTO_FIX_BY_CODE: Readonly<Record<string, string>> = {
|
|
4
5
|
EVIDENCE_MISSING: 'Ejecuta una auditoría para generar .ai_evidence.json.',
|
|
@@ -28,6 +29,7 @@ export type EnterpriseAiGateCheckResult = {
|
|
|
28
29
|
violations: ReturnType<typeof evaluateAiGate>['violations'];
|
|
29
30
|
warnings: ReadonlyArray<string>;
|
|
30
31
|
auto_fixes: ReadonlyArray<string>;
|
|
32
|
+
learning_context: SddLearningContext | null;
|
|
31
33
|
evidence: ReturnType<typeof evaluateAiGate>['evidence'];
|
|
32
34
|
mcp_receipt: ReturnType<typeof evaluateAiGate>['mcp_receipt'];
|
|
33
35
|
skills_contract: ReturnType<typeof evaluateAiGate>['skills_contract'];
|
|
@@ -99,7 +101,10 @@ const buildWarnings = (evaluation: ReturnType<typeof evaluateAiGate>): ReadonlyA
|
|
|
99
101
|
return warnings;
|
|
100
102
|
};
|
|
101
103
|
|
|
102
|
-
const buildAutoFixes = (
|
|
104
|
+
const buildAutoFixes = (
|
|
105
|
+
evaluation: ReturnType<typeof evaluateAiGate>,
|
|
106
|
+
learningContext: SddLearningContext | null
|
|
107
|
+
): ReadonlyArray<string> => {
|
|
103
108
|
const fixes: string[] = [];
|
|
104
109
|
const emittedCodes = new Set<string>();
|
|
105
110
|
for (const violation of evaluation.violations) {
|
|
@@ -113,6 +118,11 @@ const buildAutoFixes = (evaluation: ReturnType<typeof evaluateAiGate>): Readonly
|
|
|
113
118
|
fixes.push(fix);
|
|
114
119
|
emittedCodes.add(violation.code);
|
|
115
120
|
}
|
|
121
|
+
for (const recommendation of learningContext?.recommended_actions ?? []) {
|
|
122
|
+
if (!fixes.includes(recommendation)) {
|
|
123
|
+
fixes.push(recommendation);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
116
126
|
return fixes;
|
|
117
127
|
};
|
|
118
128
|
|
|
@@ -143,8 +153,11 @@ export const runEnterpriseAiGateCheck = (params: {
|
|
|
143
153
|
});
|
|
144
154
|
const branch = evaluation.repo_state.git.branch;
|
|
145
155
|
const timestamp = evaluation.evidence.source.generated_at;
|
|
156
|
+
const learningContext = readSddLearningContext({
|
|
157
|
+
repoRoot: params.repoRoot,
|
|
158
|
+
});
|
|
146
159
|
const warnings = buildWarnings(evaluation);
|
|
147
|
-
const autoFixes = buildAutoFixes(evaluation);
|
|
160
|
+
const autoFixes = buildAutoFixes(evaluation, learningContext);
|
|
148
161
|
const message = buildMessage(evaluation);
|
|
149
162
|
|
|
150
163
|
return {
|
|
@@ -163,6 +176,7 @@ export const runEnterpriseAiGateCheck = (params: {
|
|
|
163
176
|
violations: evaluation.violations,
|
|
164
177
|
warnings,
|
|
165
178
|
auto_fixes: autoFixes,
|
|
179
|
+
learning_context: learningContext,
|
|
166
180
|
evidence: evaluation.evidence,
|
|
167
181
|
mcp_receipt: evaluation.mcp_receipt,
|
|
168
182
|
skills_contract: evaluation.skills_contract,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { evaluateAiGate, type AiGateStage, type AiGateViolation } from '../gate/evaluateAiGate';
|
|
2
2
|
import { collectWorktreeAtomicSlices } from '../git/worktreeAtomicSlices';
|
|
3
|
+
import { readSddLearningContext, type SddLearningContext } from '../sdd/learningInsights';
|
|
3
4
|
|
|
4
5
|
type AutoExecuteAction = 'proceed' | 'ask';
|
|
5
6
|
type AutoExecutePhase = 'GREEN' | 'RED';
|
|
@@ -154,6 +155,7 @@ export type EnterpriseAutoExecuteAiStartResult = {
|
|
|
154
155
|
confidence_pct: number;
|
|
155
156
|
reason_code: string;
|
|
156
157
|
next_action: AutoExecuteNextAction;
|
|
158
|
+
learning_context: SddLearningContext | null;
|
|
157
159
|
gate: {
|
|
158
160
|
stage: ReturnType<typeof evaluateAiGate>['stage'];
|
|
159
161
|
status: ReturnType<typeof evaluateAiGate>['status'];
|
|
@@ -174,6 +176,9 @@ export const runEnterpriseAutoExecuteAiStart = (params: {
|
|
|
174
176
|
stage,
|
|
175
177
|
requireMcpReceipt: params.requireMcpReceipt ?? false,
|
|
176
178
|
});
|
|
179
|
+
const learningContext = readSddLearningContext({
|
|
180
|
+
repoRoot: params.repoRoot,
|
|
181
|
+
});
|
|
177
182
|
const firstViolation = evaluation.violations[0];
|
|
178
183
|
const reasonCode = firstViolation?.code ?? 'READY';
|
|
179
184
|
const action: AutoExecuteAction = evaluation.allowed ? 'proceed' : 'ask';
|
|
@@ -186,12 +191,16 @@ export const runEnterpriseAutoExecuteAiStart = (params: {
|
|
|
186
191
|
}
|
|
187
192
|
: nextActionFromViolation(firstViolation, params.repoRoot);
|
|
188
193
|
|
|
189
|
-
|
|
194
|
+
let message = toHumanMessage({
|
|
190
195
|
action,
|
|
191
196
|
confidencePct,
|
|
192
197
|
reasonCode,
|
|
193
198
|
});
|
|
194
|
-
|
|
199
|
+
let instruction = nextAction.message;
|
|
200
|
+
if (learningContext?.recommended_actions[0]) {
|
|
201
|
+
message = `${message} Learning: ${learningContext.recommended_actions[0]}`;
|
|
202
|
+
instruction = `${instruction} Learning: ${learningContext.recommended_actions[0]}`;
|
|
203
|
+
}
|
|
195
204
|
const force = action === 'ask' && confidencePct < 50;
|
|
196
205
|
|
|
197
206
|
return {
|
|
@@ -210,6 +219,7 @@ export const runEnterpriseAutoExecuteAiStart = (params: {
|
|
|
210
219
|
confidence_pct: confidencePct,
|
|
211
220
|
reason_code: reasonCode,
|
|
212
221
|
next_action: nextAction,
|
|
222
|
+
learning_context: learningContext,
|
|
213
223
|
gate: {
|
|
214
224
|
stage: evaluation.stage,
|
|
215
225
|
status: evaluation.status,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { evaluateAiGate, type AiGateStage, type AiGateViolation } from '../gate/evaluateAiGate';
|
|
2
2
|
import { collectWorktreeAtomicSlices } from '../git/worktreeAtomicSlices';
|
|
3
|
+
import { readSddLearningContext, type SddLearningContext } from '../sdd/learningInsights';
|
|
3
4
|
|
|
4
5
|
const ACTIONABLE_HINTS_BY_CODE: Readonly<Record<string, string>> = {
|
|
5
6
|
EVIDENCE_MISSING: 'Ejecuta una auditoría (1/2/3/4) para regenerar .ai_evidence.json.',
|
|
@@ -44,6 +45,7 @@ const buildPreFlightHints = (params: {
|
|
|
44
45
|
status: ReturnType<typeof evaluateAiGate>['status'];
|
|
45
46
|
violations: ReadonlyArray<AiGateViolation>;
|
|
46
47
|
upstream: string | null;
|
|
48
|
+
learningContext: SddLearningContext | null;
|
|
47
49
|
}): ReadonlyArray<string> => {
|
|
48
50
|
const hints: string[] = [];
|
|
49
51
|
const emittedCodes = new Set<string>();
|
|
@@ -89,6 +91,14 @@ const buildPreFlightHints = (params: {
|
|
|
89
91
|
hints.push('Corrige la causa bloqueante y vuelve a ejecutar el pre-flight.');
|
|
90
92
|
}
|
|
91
93
|
}
|
|
94
|
+
if (params.learningContext) {
|
|
95
|
+
hints.push(
|
|
96
|
+
`LEARNING_CONTEXT: change=${params.learningContext.change} file=${params.learningContext.path}`
|
|
97
|
+
);
|
|
98
|
+
if (params.learningContext.recommended_actions[0]) {
|
|
99
|
+
hints.push(`LEARNING_NEXT_ACTION: ${params.learningContext.recommended_actions[0]}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
92
102
|
return hints;
|
|
93
103
|
};
|
|
94
104
|
|
|
@@ -111,6 +121,7 @@ export type EnterprisePreFlightCheckResult = {
|
|
|
111
121
|
skills_contract: ReturnType<typeof evaluateAiGate>['skills_contract'];
|
|
112
122
|
repo_state: ReturnType<typeof evaluateAiGate>['repo_state'];
|
|
113
123
|
hints: ReadonlyArray<string>;
|
|
124
|
+
learning_context: SddLearningContext | null;
|
|
114
125
|
ast_analysis: null;
|
|
115
126
|
tdd_status: null;
|
|
116
127
|
};
|
|
@@ -126,6 +137,9 @@ export const runEnterprisePreFlightCheck = (params: {
|
|
|
126
137
|
stage: params.stage,
|
|
127
138
|
requireMcpReceipt: params.requireMcpReceipt ?? false,
|
|
128
139
|
});
|
|
140
|
+
const learningContext = readSddLearningContext({
|
|
141
|
+
repoRoot: params.repoRoot,
|
|
142
|
+
});
|
|
129
143
|
|
|
130
144
|
const hints = buildPreFlightHints({
|
|
131
145
|
repoRoot: params.repoRoot,
|
|
@@ -133,6 +147,7 @@ export const runEnterprisePreFlightCheck = (params: {
|
|
|
133
147
|
status: evaluation.status,
|
|
134
148
|
violations: evaluation.violations,
|
|
135
149
|
upstream: evaluation.repo_state.git.upstream,
|
|
150
|
+
learningContext,
|
|
136
151
|
});
|
|
137
152
|
const phase: 'GREEN' | 'RED' = evaluation.allowed ? 'GREEN' : 'RED';
|
|
138
153
|
const message = evaluation.allowed
|
|
@@ -161,6 +176,7 @@ export const runEnterprisePreFlightCheck = (params: {
|
|
|
161
176
|
skills_contract: evaluation.skills_contract,
|
|
162
177
|
repo_state: evaluation.repo_state,
|
|
163
178
|
hints,
|
|
179
|
+
learning_context: learningContext,
|
|
164
180
|
ast_analysis: null,
|
|
165
181
|
tdd_status: null,
|
|
166
182
|
},
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { readSddSession } from './sessionStore';
|
|
4
|
+
|
|
5
|
+
type LearningArtifact = {
|
|
6
|
+
generated_at?: unknown;
|
|
7
|
+
failed_patterns?: unknown;
|
|
8
|
+
successful_patterns?: unknown;
|
|
9
|
+
rule_updates?: unknown;
|
|
10
|
+
gate_anomalies?: unknown;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type SddLearningContext = {
|
|
14
|
+
change: string;
|
|
15
|
+
path: string;
|
|
16
|
+
generated_at: string | null;
|
|
17
|
+
failed_patterns: string[];
|
|
18
|
+
successful_patterns: string[];
|
|
19
|
+
rule_updates: string[];
|
|
20
|
+
gate_anomalies: string[];
|
|
21
|
+
recommended_actions: string[];
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const toStringArray = (value: unknown): string[] => {
|
|
25
|
+
if (!Array.isArray(value)) {
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
return value.filter((item): item is string => typeof item === 'string');
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const toRecommendedActions = (ruleUpdates: string[]): string[] => {
|
|
32
|
+
const actions: string[] = [];
|
|
33
|
+
for (const rule of ruleUpdates) {
|
|
34
|
+
if (rule === 'evidence.bootstrap.required' || rule === 'evidence.rebuild.required') {
|
|
35
|
+
actions.push(
|
|
36
|
+
'Regenera evidencia y vuelve a validar PRE_WRITE para estabilizar el gate.'
|
|
37
|
+
);
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (rule.startsWith('ai-gate.violation.')) {
|
|
41
|
+
actions.push('Corrige la violación de gate indicada y reejecuta validate/hook.');
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (rule.startsWith('sdd.')) {
|
|
45
|
+
actions.push('Completa el contrato SDD del cambio activo antes de cerrar stage.');
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return [...new Set(actions)];
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export const readSddLearningContext = (params: {
|
|
53
|
+
repoRoot: string;
|
|
54
|
+
change?: string | null;
|
|
55
|
+
}): SddLearningContext | null => {
|
|
56
|
+
const repoRoot = resolve(params.repoRoot);
|
|
57
|
+
const explicitChange = params.change?.trim().toLowerCase() ?? null;
|
|
58
|
+
const session = readSddSession(repoRoot);
|
|
59
|
+
const change = explicitChange ?? session.changeId ?? null;
|
|
60
|
+
if (!change) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const relativePath = `openspec/changes/${change}/learning.json`;
|
|
65
|
+
const absolutePath = resolve(repoRoot, relativePath);
|
|
66
|
+
if (!existsSync(absolutePath)) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
let parsed: LearningArtifact;
|
|
71
|
+
try {
|
|
72
|
+
parsed = JSON.parse(readFileSync(absolutePath, 'utf8')) as LearningArtifact;
|
|
73
|
+
} catch {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const ruleUpdates = toStringArray(parsed.rule_updates);
|
|
78
|
+
return {
|
|
79
|
+
change,
|
|
80
|
+
path: relativePath,
|
|
81
|
+
generated_at:
|
|
82
|
+
typeof parsed.generated_at === 'string' && parsed.generated_at.trim().length > 0
|
|
83
|
+
? parsed.generated_at
|
|
84
|
+
: null,
|
|
85
|
+
failed_patterns: toStringArray(parsed.failed_patterns),
|
|
86
|
+
successful_patterns: toStringArray(parsed.successful_patterns),
|
|
87
|
+
rule_updates: ruleUpdates,
|
|
88
|
+
gate_anomalies: toStringArray(parsed.gate_anomalies),
|
|
89
|
+
recommended_actions: toRecommendedActions(ruleUpdates),
|
|
90
|
+
};
|
|
91
|
+
};
|
|
@@ -11,6 +11,18 @@ const SDD_STATUS_SECTION = {
|
|
|
11
11
|
end: '<!-- PUMUKI:END SDD_STATUS -->',
|
|
12
12
|
} as const;
|
|
13
13
|
|
|
14
|
+
const TRACKING_HUB_SECTION = {
|
|
15
|
+
id: 'pumuki-sdd-sync',
|
|
16
|
+
begin: '<!-- PUMUKI:BEGIN SDD_SYNC_STATUS -->',
|
|
17
|
+
end: '<!-- PUMUKI:END SDD_SYNC_STATUS -->',
|
|
18
|
+
} as const;
|
|
19
|
+
|
|
20
|
+
const OPERATIONAL_SUMMARY_SECTION = {
|
|
21
|
+
id: 'pumuki-sdd-sync',
|
|
22
|
+
begin: '<!-- PUMUKI:BEGIN SDD_SYNC_STATUS -->',
|
|
23
|
+
end: '<!-- PUMUKI:END SDD_SYNC_STATUS -->',
|
|
24
|
+
} as const;
|
|
25
|
+
|
|
14
26
|
type ManagedSectionSyncResult = {
|
|
15
27
|
sectionId: string;
|
|
16
28
|
updated: boolean;
|
|
@@ -24,11 +36,15 @@ export type SddSyncDocsManagedSection = {
|
|
|
24
36
|
beginMarker: string;
|
|
25
37
|
endMarker: string;
|
|
26
38
|
renderBody: (repoRoot: string) => string;
|
|
39
|
+
createIfMissing?: boolean;
|
|
27
40
|
};
|
|
28
41
|
|
|
29
42
|
export type SddSyncDocsTarget = {
|
|
30
43
|
path: string;
|
|
31
|
-
sections
|
|
44
|
+
sections?: ReadonlyArray<SddSyncDocsManagedSection>;
|
|
45
|
+
renderWholeFile?: (repoRoot: string, currentSource: string) => string;
|
|
46
|
+
optional?: boolean;
|
|
47
|
+
bootstrapIfMissing?: string | ((repoRoot: string) => string);
|
|
32
48
|
};
|
|
33
49
|
|
|
34
50
|
export type SddSyncDocsFileResult = {
|
|
@@ -182,7 +198,51 @@ const formatSddStatusManagedBody = (repoRoot: string): string => {
|
|
|
182
198
|
].join('\n');
|
|
183
199
|
};
|
|
184
200
|
|
|
185
|
-
const
|
|
201
|
+
const formatTrackingManagedBody = (repoRoot: string): string => {
|
|
202
|
+
const status = readSddStatus(repoRoot);
|
|
203
|
+
return [
|
|
204
|
+
`- source: pumuki sdd sync-docs`,
|
|
205
|
+
`- repo_root: ${status.repoRoot}`,
|
|
206
|
+
`- openspec_installed: ${status.openspec.installed ? 'yes' : 'no'}`,
|
|
207
|
+
`- openspec_version: ${status.openspec.version ?? 'unknown'}`,
|
|
208
|
+
`- sdd_session_active: ${status.session.active ? 'yes' : 'no'}`,
|
|
209
|
+
`- sdd_session_valid: ${status.session.valid ? 'yes' : 'no'}`,
|
|
210
|
+
`- sdd_session_change: ${status.session.changeId ?? 'none'}`,
|
|
211
|
+
].join('\n');
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const renderLastRunJson = (repoRoot: string, currentSource: string): string => {
|
|
215
|
+
let parsed: unknown;
|
|
216
|
+
try {
|
|
217
|
+
parsed = JSON.parse(currentSource);
|
|
218
|
+
} catch {
|
|
219
|
+
throw new Error('[pumuki][sdd] sync-docs invalid JSON in docs/validation/refactor/last-run.json');
|
|
220
|
+
}
|
|
221
|
+
if (parsed === null || Array.isArray(parsed) || typeof parsed !== 'object') {
|
|
222
|
+
throw new Error('[pumuki][sdd] sync-docs expected object JSON in docs/validation/refactor/last-run.json');
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const status = readSddStatus(repoRoot);
|
|
226
|
+
const payload = parsed as Record<string, unknown>;
|
|
227
|
+
const next = {
|
|
228
|
+
...payload,
|
|
229
|
+
pumuki_sdd_status: {
|
|
230
|
+
source: 'pumuki sdd sync-docs',
|
|
231
|
+
repo_root: status.repoRoot,
|
|
232
|
+
openspec_installed: status.openspec.installed,
|
|
233
|
+
openspec_version: status.openspec.version ?? null,
|
|
234
|
+
openspec_project_initialized: status.openspec.projectInitialized,
|
|
235
|
+
openspec_compatible: status.openspec.compatible,
|
|
236
|
+
session_active: status.session.active,
|
|
237
|
+
session_valid: status.session.valid,
|
|
238
|
+
session_change: status.session.changeId ?? null,
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
return `${JSON.stringify(next, null, 2)}\n`;
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
const PRIMARY_SYNC_DOCS_TARGETS: ReadonlyArray<SddSyncDocsTarget> = [
|
|
186
246
|
{
|
|
187
247
|
path: 'docs/technical/08-validation/refactor/pumuki-integration-feedback.md',
|
|
188
248
|
sections: [
|
|
@@ -196,10 +256,151 @@ const DEFAULT_SYNC_DOCS_TARGETS: ReadonlyArray<SddSyncDocsTarget> = [
|
|
|
196
256
|
},
|
|
197
257
|
];
|
|
198
258
|
|
|
259
|
+
const OPTIONAL_SYNC_DOCS_TARGETS: ReadonlyArray<SddSyncDocsTarget> = [
|
|
260
|
+
{
|
|
261
|
+
path: 'docs/strategy/ruralgo-tracking-hub.md',
|
|
262
|
+
optional: true,
|
|
263
|
+
sections: [
|
|
264
|
+
{
|
|
265
|
+
id: TRACKING_HUB_SECTION.id,
|
|
266
|
+
beginMarker: TRACKING_HUB_SECTION.begin,
|
|
267
|
+
endMarker: TRACKING_HUB_SECTION.end,
|
|
268
|
+
renderBody: formatTrackingManagedBody,
|
|
269
|
+
createIfMissing: true,
|
|
270
|
+
},
|
|
271
|
+
],
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
path: 'docs/technical/08-validation/refactor/operational-summary.md',
|
|
275
|
+
optional: true,
|
|
276
|
+
sections: [
|
|
277
|
+
{
|
|
278
|
+
id: OPERATIONAL_SUMMARY_SECTION.id,
|
|
279
|
+
beginMarker: OPERATIONAL_SUMMARY_SECTION.begin,
|
|
280
|
+
endMarker: OPERATIONAL_SUMMARY_SECTION.end,
|
|
281
|
+
renderBody: formatTrackingManagedBody,
|
|
282
|
+
createIfMissing: true,
|
|
283
|
+
},
|
|
284
|
+
],
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
path: 'docs/validation/refactor/last-run.json',
|
|
288
|
+
optional: true,
|
|
289
|
+
renderWholeFile: renderLastRunJson,
|
|
290
|
+
},
|
|
291
|
+
];
|
|
292
|
+
|
|
293
|
+
const DEFAULT_SYNC_DOCS_TARGETS: ReadonlyArray<SddSyncDocsTarget> = [
|
|
294
|
+
...PRIMARY_SYNC_DOCS_TARGETS,
|
|
295
|
+
...OPTIONAL_SYNC_DOCS_TARGETS,
|
|
296
|
+
];
|
|
297
|
+
|
|
199
298
|
export const SDD_SYNC_DOCS_CANONICAL_FILES = DEFAULT_SYNC_DOCS_TARGETS.map(
|
|
200
299
|
(target) => target.path
|
|
201
300
|
);
|
|
202
301
|
|
|
302
|
+
const resolveSyncDocsTargets = (
|
|
303
|
+
repoRoot: string,
|
|
304
|
+
targets?: ReadonlyArray<SddSyncDocsTarget>
|
|
305
|
+
): ReadonlyArray<SddSyncDocsTarget> => {
|
|
306
|
+
if (targets) {
|
|
307
|
+
return targets;
|
|
308
|
+
}
|
|
309
|
+
return DEFAULT_SYNC_DOCS_TARGETS.filter((target) => {
|
|
310
|
+
if (!target.optional) {
|
|
311
|
+
return true;
|
|
312
|
+
}
|
|
313
|
+
return existsSync(resolve(repoRoot, target.path));
|
|
314
|
+
});
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
const OPENSPEC_AUTO_SYNC_SECTION = {
|
|
318
|
+
id: 'pumuki-auto-sync',
|
|
319
|
+
begin: '<!-- PUMUKI:BEGIN AUTO_SYNC_STATUS -->',
|
|
320
|
+
end: '<!-- PUMUKI:END AUTO_SYNC_STATUS -->',
|
|
321
|
+
} as const;
|
|
322
|
+
|
|
323
|
+
const formatOpenSpecAutoSyncBody = (params: {
|
|
324
|
+
change: string;
|
|
325
|
+
stage: SddStage | null;
|
|
326
|
+
task: string | null;
|
|
327
|
+
now: () => Date;
|
|
328
|
+
}): string =>
|
|
329
|
+
[
|
|
330
|
+
`- source: pumuki sdd auto-sync`,
|
|
331
|
+
`- change: ${params.change}`,
|
|
332
|
+
`- stage: ${params.stage ?? 'none'}`,
|
|
333
|
+
`- task: ${params.task ?? 'none'}`,
|
|
334
|
+
`- updated_at: ${params.now().toISOString()}`,
|
|
335
|
+
].join('\n');
|
|
336
|
+
|
|
337
|
+
const buildOpenSpecAutoSyncTargets = (params: {
|
|
338
|
+
change: string;
|
|
339
|
+
stage: SddStage | null;
|
|
340
|
+
task: string | null;
|
|
341
|
+
now: () => Date;
|
|
342
|
+
}): ReadonlyArray<SddSyncDocsTarget> => {
|
|
343
|
+
const buildDoc = (title: string): string =>
|
|
344
|
+
[
|
|
345
|
+
`# ${title}`,
|
|
346
|
+
'',
|
|
347
|
+
OPENSPEC_AUTO_SYNC_SECTION.begin,
|
|
348
|
+
'- source: bootstrap',
|
|
349
|
+
OPENSPEC_AUTO_SYNC_SECTION.end,
|
|
350
|
+
'',
|
|
351
|
+
].join('\n');
|
|
352
|
+
|
|
353
|
+
const renderBody = () =>
|
|
354
|
+
formatOpenSpecAutoSyncBody({
|
|
355
|
+
change: params.change,
|
|
356
|
+
stage: params.stage,
|
|
357
|
+
task: params.task,
|
|
358
|
+
now: params.now,
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
return [
|
|
362
|
+
{
|
|
363
|
+
path: `openspec/changes/${params.change}/tasks.md`,
|
|
364
|
+
sections: [
|
|
365
|
+
{
|
|
366
|
+
id: OPENSPEC_AUTO_SYNC_SECTION.id,
|
|
367
|
+
beginMarker: OPENSPEC_AUTO_SYNC_SECTION.begin,
|
|
368
|
+
endMarker: OPENSPEC_AUTO_SYNC_SECTION.end,
|
|
369
|
+
renderBody,
|
|
370
|
+
createIfMissing: true,
|
|
371
|
+
},
|
|
372
|
+
],
|
|
373
|
+
bootstrapIfMissing: buildDoc('Tasks'),
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
path: `openspec/changes/${params.change}/design.md`,
|
|
377
|
+
sections: [
|
|
378
|
+
{
|
|
379
|
+
id: OPENSPEC_AUTO_SYNC_SECTION.id,
|
|
380
|
+
beginMarker: OPENSPEC_AUTO_SYNC_SECTION.begin,
|
|
381
|
+
endMarker: OPENSPEC_AUTO_SYNC_SECTION.end,
|
|
382
|
+
renderBody,
|
|
383
|
+
createIfMissing: true,
|
|
384
|
+
},
|
|
385
|
+
],
|
|
386
|
+
bootstrapIfMissing: buildDoc('Design'),
|
|
387
|
+
},
|
|
388
|
+
{
|
|
389
|
+
path: `openspec/changes/${params.change}/retrospective.md`,
|
|
390
|
+
sections: [
|
|
391
|
+
{
|
|
392
|
+
id: OPENSPEC_AUTO_SYNC_SECTION.id,
|
|
393
|
+
beginMarker: OPENSPEC_AUTO_SYNC_SECTION.begin,
|
|
394
|
+
endMarker: OPENSPEC_AUTO_SYNC_SECTION.end,
|
|
395
|
+
renderBody,
|
|
396
|
+
createIfMissing: true,
|
|
397
|
+
},
|
|
398
|
+
],
|
|
399
|
+
bootstrapIfMissing: buildDoc('Retrospective'),
|
|
400
|
+
},
|
|
401
|
+
];
|
|
402
|
+
};
|
|
403
|
+
|
|
203
404
|
const applyManagedSection = (params: {
|
|
204
405
|
filePath: string;
|
|
205
406
|
source: string;
|
|
@@ -207,14 +408,46 @@ const applyManagedSection = (params: {
|
|
|
207
408
|
endMarker: string;
|
|
208
409
|
renderedBody: string;
|
|
209
410
|
sectionId: string;
|
|
411
|
+
createIfMissing?: boolean;
|
|
210
412
|
}): {
|
|
211
413
|
nextSource: string;
|
|
212
414
|
result: ManagedSectionSyncResult;
|
|
213
415
|
} => {
|
|
214
416
|
const beginIndex = params.source.indexOf(params.beginMarker);
|
|
215
417
|
const endIndex = params.source.indexOf(params.endMarker);
|
|
216
|
-
|
|
217
|
-
|
|
418
|
+
const missingBegin = beginIndex === -1;
|
|
419
|
+
const missingEnd = endIndex === -1;
|
|
420
|
+
|
|
421
|
+
if (missingBegin || missingEnd) {
|
|
422
|
+
if (params.createIfMissing === true && missingBegin && missingEnd) {
|
|
423
|
+
const beforeBody = '';
|
|
424
|
+
const afterBody = normalizeSectionBody(params.renderedBody);
|
|
425
|
+
const block = `${params.beginMarker}\n${params.renderedBody}\n${params.endMarker}`;
|
|
426
|
+
const sourceTrimmedEnd = params.source.replace(/\s*$/, '');
|
|
427
|
+
const nextSource =
|
|
428
|
+
sourceTrimmedEnd.length === 0
|
|
429
|
+
? `${block}\n`
|
|
430
|
+
: `${sourceTrimmedEnd}\n\n${block}\n`;
|
|
431
|
+
return {
|
|
432
|
+
nextSource,
|
|
433
|
+
result: {
|
|
434
|
+
sectionId: params.sectionId,
|
|
435
|
+
updated: true,
|
|
436
|
+
before: beforeBody,
|
|
437
|
+
after: afterBody,
|
|
438
|
+
diffMarkdown: buildSectionDiffMarkdown({
|
|
439
|
+
sectionId: params.sectionId,
|
|
440
|
+
before: beforeBody,
|
|
441
|
+
after: afterBody,
|
|
442
|
+
}),
|
|
443
|
+
},
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
throw new Error(
|
|
447
|
+
`[pumuki][sdd] sync-docs conflict in ${params.filePath}: expected managed markers ${params.beginMarker} ... ${params.endMarker}`
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
if (endIndex < beginIndex) {
|
|
218
451
|
throw new Error(
|
|
219
452
|
`[pumuki][sdd] sync-docs conflict in ${params.filePath}: expected managed markers ${params.beginMarker} ... ${params.endMarker}`
|
|
220
453
|
);
|
|
@@ -369,7 +602,7 @@ export const runSddSyncDocs = (params?: {
|
|
|
369
602
|
flagName: '--from-evidence',
|
|
370
603
|
})
|
|
371
604
|
: null;
|
|
372
|
-
const targets = params?.targets
|
|
605
|
+
const targets = resolveSyncDocsTargets(repoRoot, params?.targets);
|
|
373
606
|
const now = params?.now ?? (() => new Date());
|
|
374
607
|
const evidenceReader =
|
|
375
608
|
params?.evidenceReader ??
|
|
@@ -381,45 +614,88 @@ export const runSddSyncDocs = (params?: {
|
|
|
381
614
|
: undefined
|
|
382
615
|
));
|
|
383
616
|
|
|
384
|
-
const updates
|
|
617
|
+
const updates: Array<{
|
|
618
|
+
relativePath: string;
|
|
619
|
+
absolutePath: string;
|
|
620
|
+
currentSource: string;
|
|
621
|
+
nextSource: string;
|
|
622
|
+
sections: ManagedSectionSyncResult[];
|
|
623
|
+
}> = [];
|
|
624
|
+
|
|
625
|
+
for (const target of targets) {
|
|
385
626
|
const absolutePath = resolve(repoRoot, target.path);
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
627
|
+
const exists = existsSync(absolutePath);
|
|
628
|
+
let currentSource = '';
|
|
629
|
+
|
|
630
|
+
if (!exists) {
|
|
631
|
+
if (target.bootstrapIfMissing !== undefined) {
|
|
632
|
+
currentSource =
|
|
633
|
+
typeof target.bootstrapIfMissing === 'function'
|
|
634
|
+
? target.bootstrapIfMissing(repoRoot)
|
|
635
|
+
: target.bootstrapIfMissing;
|
|
636
|
+
} else if (target.optional) {
|
|
637
|
+
continue;
|
|
638
|
+
} else {
|
|
639
|
+
throw new Error(
|
|
640
|
+
`[pumuki][sdd] sync-docs missing canonical file: ${target.path}`
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
} else {
|
|
644
|
+
currentSource = readFileSync(absolutePath, 'utf8');
|
|
390
645
|
}
|
|
391
646
|
|
|
392
|
-
const currentSource = readFileSync(absolutePath, 'utf8');
|
|
393
647
|
let nextSource = currentSource;
|
|
394
648
|
const sectionUpdates: ManagedSectionSyncResult[] = [];
|
|
395
649
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
650
|
+
if (target.renderWholeFile) {
|
|
651
|
+
nextSource = target.renderWholeFile(repoRoot, nextSource);
|
|
652
|
+
sectionUpdates.push({
|
|
653
|
+
sectionId: 'file-content',
|
|
654
|
+
updated: normalizeSectionBody(currentSource) !== normalizeSectionBody(nextSource),
|
|
655
|
+
before: normalizeSectionBody(currentSource),
|
|
656
|
+
after: normalizeSectionBody(nextSource),
|
|
657
|
+
diffMarkdown: buildSectionDiffMarkdown({
|
|
658
|
+
sectionId: 'file-content',
|
|
659
|
+
before: normalizeSectionBody(currentSource),
|
|
660
|
+
after: normalizeSectionBody(nextSource),
|
|
661
|
+
}),
|
|
404
662
|
});
|
|
405
|
-
|
|
406
|
-
|
|
663
|
+
} else {
|
|
664
|
+
if (!target.sections || target.sections.length === 0) {
|
|
665
|
+
throw new Error(
|
|
666
|
+
`[pumuki][sdd] sync-docs invalid target configuration for ${target.path}: expected sections or renderWholeFile`
|
|
667
|
+
);
|
|
668
|
+
}
|
|
669
|
+
for (const section of target.sections) {
|
|
670
|
+
const update = applyManagedSection({
|
|
671
|
+
filePath: target.path,
|
|
672
|
+
source: nextSource,
|
|
673
|
+
beginMarker: section.beginMarker,
|
|
674
|
+
endMarker: section.endMarker,
|
|
675
|
+
renderedBody: section.renderBody(repoRoot),
|
|
676
|
+
sectionId: section.id,
|
|
677
|
+
createIfMissing: section.createIfMissing,
|
|
678
|
+
});
|
|
679
|
+
nextSource = update.nextSource;
|
|
680
|
+
sectionUpdates.push(update.result);
|
|
681
|
+
}
|
|
407
682
|
}
|
|
408
683
|
|
|
409
|
-
|
|
684
|
+
updates.push({
|
|
410
685
|
relativePath: target.path,
|
|
411
686
|
absolutePath,
|
|
412
687
|
currentSource,
|
|
413
688
|
nextSource,
|
|
414
689
|
sections: sectionUpdates,
|
|
415
|
-
};
|
|
416
|
-
}
|
|
690
|
+
});
|
|
691
|
+
}
|
|
417
692
|
|
|
418
693
|
if (!dryRun) {
|
|
419
694
|
for (const update of updates) {
|
|
420
|
-
if (update.currentSource === update.nextSource) {
|
|
695
|
+
if (update.currentSource === update.nextSource && existsSync(update.absolutePath)) {
|
|
421
696
|
continue;
|
|
422
697
|
}
|
|
698
|
+
mkdirSync(dirname(update.absolutePath), { recursive: true });
|
|
423
699
|
writeFileSync(update.absolutePath, update.nextSource, 'utf8');
|
|
424
700
|
}
|
|
425
701
|
}
|
|
@@ -557,17 +833,30 @@ export const runSddAutoSync = (params?: {
|
|
|
557
833
|
if (!change) {
|
|
558
834
|
throw new Error('[pumuki][sdd] auto-sync requires --change=<change-id>.');
|
|
559
835
|
}
|
|
836
|
+
const repoRoot = resolve(params?.repoRoot ?? process.cwd());
|
|
837
|
+
const now = params?.now ?? (() => new Date());
|
|
838
|
+
const targets =
|
|
839
|
+
params?.targets ??
|
|
840
|
+
[
|
|
841
|
+
...resolveSyncDocsTargets(repoRoot),
|
|
842
|
+
...buildOpenSpecAutoSyncTargets({
|
|
843
|
+
change,
|
|
844
|
+
stage: params?.stage ?? null,
|
|
845
|
+
task: params?.task?.trim() ? params.task.trim() : null,
|
|
846
|
+
now,
|
|
847
|
+
}),
|
|
848
|
+
];
|
|
560
849
|
|
|
561
850
|
const syncResult = runSddSyncDocs({
|
|
562
|
-
repoRoot
|
|
851
|
+
repoRoot,
|
|
563
852
|
dryRun: params?.dryRun,
|
|
564
853
|
change,
|
|
565
854
|
stage: params?.stage,
|
|
566
855
|
task: params?.task,
|
|
567
856
|
fromEvidencePath: params?.fromEvidencePath,
|
|
568
|
-
now
|
|
857
|
+
now,
|
|
569
858
|
evidenceReader: params?.evidenceReader,
|
|
570
|
-
targets
|
|
859
|
+
targets,
|
|
571
860
|
});
|
|
572
861
|
|
|
573
862
|
if (!syncResult.learning) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pumuki",
|
|
3
|
-
"version": "6.3.
|
|
3
|
+
"version": "6.3.45",
|
|
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": {
|