pumuki 6.3.35 → 6.3.37

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/VERSION CHANGED
@@ -1 +1 @@
1
- v6.3.24
1
+ v6.3.37
@@ -191,6 +191,7 @@ Behavior:
191
191
  - non dry-run: persists `learning.json` deterministically and reports digest/path in output.
192
192
  - `rule_updates`: deterministic recommendations derived from evidence/gate signals (`missing`, `invalid`, `blocked`, `allowed`).
193
193
  - dedicated command: `pumuki sdd learn --change=<id> [--stage=<stage>] [--task=<task>] [--dry-run] [--json]` generates/persists the same artifact without requiring `sync-docs`.
194
+ - orchestration command: `pumuki sdd auto-sync --change=<id> [--stage=<stage>] [--task=<task>] [--dry-run] [--json]` executes deterministic docs sync plus learning generation in one step.
194
195
 
195
196
  ## Gate telemetry export (optional)
196
197
 
@@ -228,7 +229,7 @@ Expected JSONL keys for enterprise audit ingestion:
228
229
  - `schema=telemetry_event_v1` with `schema_version=1.0`
229
230
  - `stage`, `gate_outcome`, `severity_counts`
230
231
  - `policy.bundle`, `policy.hash`, `policy.version`, `policy.signature`, `policy.policy_source`
231
- - `policy.validation_status`, `policy.validation_code` (when policy-as-code validation is available)
232
+ - `policy.validation_status`, `policy.validation_code` (when policy-as-code validation is available; status can be `valid|invalid|expired|unknown-source|unsigned`)
232
233
 
233
234
  ## Heuristic pilot flag
234
235
 
@@ -5,6 +5,40 @@ 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-04 (v6.3.37)
9
+
10
+ - Policy-as-code enterprise hardening shipped:
11
+ - strict mode now blocks unsigned runtime policy metadata with deterministic code `POLICY_AS_CODE_UNSIGNED`.
12
+ - lifecycle outputs now expose policy validation metadata in `status --json`, `doctor --json`, and `sdd validate --json`.
13
+ - telemetry/evidence contract now supports policy validation status `unsigned`.
14
+ - Traceability:
15
+ - implementation issue: `#606`
16
+ - implementation PR: `#608`
17
+ - tracking sync PR: `#609`
18
+ - Consumer quick verification:
19
+ - `npx --yes --package pumuki@latest pumuki status --json`
20
+ - `npx --yes --package pumuki@latest pumuki doctor --json`
21
+ - `PUMUKI_POLICY_STRICT=1 npx --yes --package pumuki@latest pumuki-pre-commit`
22
+ - expected signal:
23
+ - JSON includes `policyValidation.stages.*.validationCode`.
24
+ - strict mode blocks unsigned contracts with `POLICY_AS_CODE_UNSIGNED`.
25
+
26
+ ### 2026-03-04 (v6.3.36)
27
+
28
+ - SDD orchestration hardening shipped:
29
+ - New enterprise command `pumuki sdd auto-sync` with `--change`, optional `--stage/--task`, `--dry-run`, and `--json`.
30
+ - `auto-sync` orchestrates deterministic docs sync plus learning generation in one step.
31
+ - Fail-safe behavior preserved through the existing transactional `sync-docs` path (no partial writes on conflict).
32
+ - Traceability:
33
+ - implementation issue: `#600`
34
+ - implementation PR: `#602`
35
+ - tracking sync PR: `#604`
36
+ - Consumer quick verification:
37
+ - `npx --yes --package pumuki@latest pumuki sdd auto-sync --change=rgo-quickstart-02 --stage=PRE_WRITE --task=P12.F2.T70 --dry-run --json`
38
+ - expected signal:
39
+ - `command=pumuki sdd auto-sync` available in CLI help.
40
+ - JSON payload includes `syncDocs.updated` and `learning.path`.
41
+
8
42
  ### 2026-03-04 (v6.3.35)
9
43
 
10
44
  - SDD enterprise incremental hardening shipped:
@@ -7,8 +7,8 @@
7
7
  ## Estado actual
8
8
  - Plan activo: `docs/seguimiento-completo-validacion-ruralgo-03-03-2026.md`
9
9
  - Estado del plan: EN CURSO
10
- - Última task cerrada (`✅`): `P12.F2.T68` (nuevo comando `pumuki sdd learn`, issue `#597`, PR `#599`).
11
- - Task activa (`🚧`): `P12.F2.T69` (publicar release `6.3.35` con cierre SDD incremental en npm).
10
+ - Última task cerrada (`✅`): `P12.F2.T72` (hardening `policy-as-code` completado: issue `#606` cerrada con PR `#608`).
11
+ - Task activa (`🚧`): `P12.F2.T73` (preparar/publicar release incremental con el hardening de `#606`).
12
12
 
13
13
  ## Historial resumido
14
14
  - No se mantienen MDs históricos de seguimiento en este repositorio.
@@ -1942,11 +1942,62 @@ Criterio de salida F5:
1942
1942
  - `npx --yes tsx@4.21.0 --test integrations/sdd/__tests__/syncDocs.test.ts integrations/lifecycle/__tests__/cli.test.ts` => `38 passed, 0 failed`.
1943
1943
  - `npm run -s typecheck` => `exit 0`.
1944
1944
 
1945
- - 🚧 `P12.F2.T69` Publicar release `6.3.35` con cierre SDD incremental.
1945
+ - `P12.F2.T69` Publicar release `6.3.35` con cierre SDD incremental.
1946
+ - cierre ejecutado:
1947
+ - versión incrementada a `6.3.35` en `package.json` y `package-lock.json`.
1948
+ - release notes actualizadas con entrada `2026-03-04 (v6.3.35)` en `docs/RELEASE_NOTES.md`.
1949
+ - publicación npm ejecutada con éxito (`npm publish --access public`).
1950
+ - propagación validada:
1951
+ - `npm view pumuki dist-tags --json` => `"latest": "6.3.35"`.
1952
+ - smoke `@latest` en carpeta temporal mostrando `pumuki sdd learn ...` en `--help`.
1953
+ - evidencia:
1954
+ - `npx --yes tsx@4.21.0 --test integrations/sdd/__tests__/syncDocs.test.ts integrations/lifecycle/__tests__/cli.test.ts` => `38 passed, 0 failed`.
1955
+ - `npm run -s typecheck` => `exit 0`.
1956
+ - `npm publish --access public` => `+ pumuki@6.3.35`.
1957
+ - `npm view pumuki@6.3.35 version` => `6.3.35`.
1958
+
1959
+ - ✅ `P12.F2.T70` Siguiente SDD pendiente enterprise: comando orquestador `pumuki sdd auto-sync` (`#600`).
1960
+ - cierre ejecutado:
1961
+ - nuevo runtime `runSddAutoSync` en capa SDD para orquestar `sync-docs` + `learning` en un único paso determinista.
1962
+ - CLI ampliada con:
1963
+ - `pumuki sdd auto-sync --change=<id> --stage=<stage> --task=<task-id> [--dry-run] [--json]`.
1964
+ - cobertura de regresión añadida en `syncDocs`/`index`/`cli` y documentación actualizada en `docs/CONFIGURATION.md`.
1965
+ - issue cerrada: `#600`.
1966
+ - PR mergeada: `#602` (`commit 2be34c5`).
1967
+ - evidencia:
1968
+ - `npx --yes tsx@4.21.0 --test integrations/sdd/__tests__/syncDocs.test.ts integrations/sdd/__tests__/index.test.ts integrations/lifecycle/__tests__/cli.test.ts` => `44 passed, 0 failed`.
1969
+ - `npm run -s typecheck` => `exit 0`.
1970
+
1971
+ - ✅ `P12.F2.T71` Publicar release `6.3.36` con `pumuki sdd auto-sync` (`#603`).
1972
+ - cierre ejecutado:
1973
+ - versionado a `6.3.36` en `package.json`, `package-lock.json` y `VERSION`.
1974
+ - release notes actualizadas con entrada `2026-03-04 (v6.3.36)` en `docs/RELEASE_NOTES.md`.
1975
+ - publicación npm ejecutada con éxito (`npm publish --access public`).
1976
+ - propagación validada:
1977
+ - `npm view pumuki dist-tags --json` => `"latest": "6.3.36"`.
1978
+ - smoke `@latest` con `--help` mostrando `pumuki sdd auto-sync ...`.
1979
+ - evidencia:
1980
+ - `npx --yes tsx@4.21.0 --test integrations/sdd/__tests__/syncDocs.test.ts integrations/sdd/__tests__/index.test.ts integrations/lifecycle/__tests__/cli.test.ts` => `44 passed, 0 failed`.
1981
+ - `npm run -s typecheck` => `exit 0`.
1982
+ - `npm publish --access public` => `+ pumuki@6.3.36`.
1983
+ - `npm view pumuki@6.3.36 version` => `6.3.36`.
1984
+
1985
+ - ✅ `P12.F2.T72` Hardening enterprise policy-as-code firmada/versionada (`#606`).
1986
+ - cierre ejecutado:
1987
+ - modo estricto bloquea política no firmada con código determinista `POLICY_AS_CODE_UNSIGNED`.
1988
+ - `status`, `doctor` y `sdd validate --json` exponen metadatos de validación de policy (`source/bundle/hash/version/signature/status/code/strict`).
1989
+ - telemetría/evidence alineadas con estado adicional `unsigned`.
1990
+ - cobertura de regresión añadida para strict unsigned + metadatos lifecycle.
1991
+ - issue cerrada: `#606`.
1992
+ - PR mergeada: `#608` (`commit 881eac8`).
1993
+ - evidencia:
1994
+ - `npx --yes tsx@4.21.0 --test integrations/gate/__tests__/stagePolicies.test.ts integrations/git/__tests__/runPlatformGate.test.ts integrations/lifecycle/__tests__/status.test.ts integrations/lifecycle/__tests__/doctor.test.ts integrations/lifecycle/__tests__/cli.test.ts` => `81 passed, 0 failed`.
1995
+ - `npm run -s typecheck` => `exit 0`.
1996
+
1997
+ - 🚧 `P12.F2.T73` Preparar y publicar release con el hardening de `#606`.
1946
1998
  - salida esperada:
1947
- - bump de versión (`package.json`, `package-lock.json`) + nota de release.
1948
- - publicación npm exitosa y verificación `npm view pumuki version`.
1949
- - smoke mínimo con `npx --yes --package pumuki@latest pumuki --help`.
1999
+ - versionar release incremental y notas de publicación.
2000
+ - publicar en npm y verificar propagación `dist-tags`.
1950
2001
 
1951
2002
  Criterio de salida F6:
1952
2003
  - veredicto final trazable y cierre administrativo completo.
@@ -89,7 +89,7 @@ export type RulesetState = {
89
89
  version?: string;
90
90
  signature?: string;
91
91
  source?: string;
92
- validation_status?: 'valid' | 'invalid' | 'expired' | 'unknown-source';
92
+ validation_status?: 'valid' | 'invalid' | 'expired' | 'unknown-source' | 'unsigned';
93
93
  validation_code?: string;
94
94
  degraded_mode_enabled?: boolean;
95
95
  degraded_mode_action?: 'allow' | 'block';
@@ -49,9 +49,10 @@ export type ResolvedStagePolicy = {
49
49
  signature?: string;
50
50
  policySource?: string;
51
51
  validation?: {
52
- status: 'valid' | 'invalid' | 'expired' | 'unknown-source';
52
+ status: 'valid' | 'invalid' | 'expired' | 'unknown-source' | 'unsigned';
53
53
  code:
54
54
  | 'POLICY_AS_CODE_VALID'
55
+ | 'POLICY_AS_CODE_UNSIGNED'
55
56
  | 'POLICY_AS_CODE_CONTRACT_INVALID'
56
57
  | 'POLICY_AS_CODE_CONTRACT_EXPIRED'
57
58
  | 'POLICY_AS_CODE_SIGNATURE_MISMATCH'
@@ -307,6 +308,21 @@ const resolvePolicyAsCodeTraceMetadata = (params: {
307
308
  const contractPath = join(params.repoRoot, POLICY_AS_CODE_CONTRACT_PATH);
308
309
 
309
310
  if (!existsSync(contractPath)) {
311
+ if (strict) {
312
+ return {
313
+ version: computedVersion,
314
+ signature: computedSignature,
315
+ policySource: 'computed-local',
316
+ validation: {
317
+ status: 'unsigned',
318
+ code: 'POLICY_AS_CODE_UNSIGNED',
319
+ message:
320
+ 'Policy-as-code contract is missing; runtime policy metadata is unsigned.',
321
+ strict,
322
+ },
323
+ };
324
+ }
325
+
310
326
  return {
311
327
  version: computedVersion,
312
328
  signature: computedSignature,
@@ -10,6 +10,10 @@ import {
10
10
  import { runLifecycleInstall } from './install';
11
11
  import { runLifecycleRemove } from './remove';
12
12
  import { readLifecycleStatus } from './status';
13
+ import {
14
+ readLifecyclePolicyValidationSnapshot,
15
+ type LifecyclePolicyValidationSnapshot,
16
+ } from './policyValidationSnapshot';
13
17
  import { runLifecycleUninstall } from './uninstall';
14
18
  import { runLifecycleUpdate } from './update';
15
19
  import { runOpenSpecBootstrap } from './openSpecBootstrap';
@@ -27,6 +31,7 @@ import {
27
31
  openSddSession,
28
32
  readSddStatus,
29
33
  refreshSddSession,
34
+ runSddAutoSync,
30
35
  runSddLearn,
31
36
  runSddSyncDocs,
32
37
  type SddStage,
@@ -63,7 +68,7 @@ type LifecycleCommand =
63
68
  | 'adapter'
64
69
  | 'analytics';
65
70
 
66
- type SddCommand = 'status' | 'validate' | 'session' | 'sync-docs' | 'learn';
71
+ type SddCommand = 'status' | 'validate' | 'session' | 'sync-docs' | 'learn' | 'auto-sync';
67
72
  type LoopCommand = 'run' | 'status' | 'stop' | 'resume' | 'list' | 'export';
68
73
  type AnalyticsCommand = 'hotspots';
69
74
  type AnalyticsHotspotsCommand = 'report' | 'diagnose';
@@ -95,6 +100,10 @@ type ParsedArgs = {
95
100
  sddLearnChange?: string;
96
101
  sddLearnStage?: SddStage;
97
102
  sddLearnTask?: string;
103
+ sddAutoSyncDryRun?: boolean;
104
+ sddAutoSyncChange?: string;
105
+ sddAutoSyncStage?: SddStage;
106
+ sddAutoSyncTask?: string;
98
107
  adapterCommand?: 'install';
99
108
  adapterAgent?: AdapterAgent;
100
109
  adapterDryRun?: boolean;
@@ -130,6 +139,7 @@ Pumuki lifecycle commands:
130
139
  pumuki sdd session --close [--json]
131
140
  pumuki sdd sync-docs [--change=<change-id>] [--stage=PRE_WRITE|PRE_COMMIT|PRE_PUSH|CI] [--task=<task-id>] [--dry-run] [--json]
132
141
  pumuki sdd learn --change=<change-id> [--stage=PRE_WRITE|PRE_COMMIT|PRE_PUSH|CI] [--task=<task-id>] [--dry-run] [--json]
142
+ pumuki sdd auto-sync --change=<change-id> [--stage=PRE_WRITE|PRE_COMMIT|PRE_PUSH|CI] [--task=<task-id>] [--dry-run] [--json]
133
143
  `.trim();
134
144
 
135
145
  const LOOP_RUN_POLICY: GatePolicy = {
@@ -436,6 +446,10 @@ export const parseLifecycleCliArgs = (argv: ReadonlyArray<string>): ParsedArgs =
436
446
  let sddLearnChange: ParsedArgs['sddLearnChange'];
437
447
  let sddLearnStage: ParsedArgs['sddLearnStage'];
438
448
  let sddLearnTask: ParsedArgs['sddLearnTask'];
449
+ let sddAutoSyncDryRun = false;
450
+ let sddAutoSyncChange: ParsedArgs['sddAutoSyncChange'];
451
+ let sddAutoSyncStage: ParsedArgs['sddAutoSyncStage'];
452
+ let sddAutoSyncTask: ParsedArgs['sddAutoSyncTask'];
439
453
  let adapterCommand: ParsedArgs['adapterCommand'];
440
454
  let adapterAgent: ParsedArgs['adapterAgent'];
441
455
  let adapterDryRun = false;
@@ -615,7 +629,8 @@ export const parseLifecycleCliArgs = (argv: ReadonlyArray<string>): ParsedArgs =
615
629
  subcommandRaw !== 'validate' &&
616
630
  subcommandRaw !== 'session' &&
617
631
  subcommandRaw !== 'sync-docs' &&
618
- subcommandRaw !== 'learn'
632
+ subcommandRaw !== 'learn' &&
633
+ subcommandRaw !== 'auto-sync'
619
634
  ) {
620
635
  throw new Error(`Unsupported SDD subcommand "${subcommandRaw}".\n\n${HELP_TEXT}`);
621
636
  }
@@ -635,7 +650,11 @@ export const parseLifecycleCliArgs = (argv: ReadonlyArray<string>): ParsedArgs =
635
650
  sddLearnDryRun = true;
636
651
  continue;
637
652
  }
638
- throw new Error(`--dry-run is only supported with "pumuki sdd sync-docs" or "pumuki sdd learn".\n\n${HELP_TEXT}`);
653
+ if (sddCommand === 'auto-sync') {
654
+ sddAutoSyncDryRun = true;
655
+ continue;
656
+ }
657
+ throw new Error(`--dry-run is only supported with "pumuki sdd sync-docs", "pumuki sdd learn" or "pumuki sdd auto-sync".\n\n${HELP_TEXT}`);
639
658
  }
640
659
  if (arg.startsWith('--stage=')) {
641
660
  if (sddCommand === 'validate') {
@@ -650,7 +669,11 @@ export const parseLifecycleCliArgs = (argv: ReadonlyArray<string>): ParsedArgs =
650
669
  sddLearnStage = parseSddStage(arg.slice('--stage='.length));
651
670
  continue;
652
671
  }
653
- throw new Error(`--stage is only supported with "pumuki sdd validate", "pumuki sdd sync-docs" or "pumuki sdd learn".\n\n${HELP_TEXT}`);
672
+ if (sddCommand === 'auto-sync') {
673
+ sddAutoSyncStage = parseSddStage(arg.slice('--stage='.length));
674
+ continue;
675
+ }
676
+ throw new Error(`--stage is only supported with "pumuki sdd validate", "pumuki sdd sync-docs", "pumuki sdd learn" or "pumuki sdd auto-sync".\n\n${HELP_TEXT}`);
654
677
  }
655
678
  if (arg === '--open') {
656
679
  if (sddCommand !== 'session') {
@@ -694,7 +717,15 @@ export const parseLifecycleCliArgs = (argv: ReadonlyArray<string>): ParsedArgs =
694
717
  sddLearnChange = changeValue;
695
718
  continue;
696
719
  }
697
- throw new Error(`--change is only supported with "pumuki sdd session", "pumuki sdd sync-docs" or "pumuki sdd learn".\n\n${HELP_TEXT}`);
720
+ if (sddCommand === 'auto-sync') {
721
+ const changeValue = arg.slice('--change='.length).trim();
722
+ if (changeValue.length === 0) {
723
+ throw new Error(`Invalid --change value "${arg}".`);
724
+ }
725
+ sddAutoSyncChange = changeValue;
726
+ continue;
727
+ }
728
+ throw new Error(`--change is only supported with "pumuki sdd session", "pumuki sdd sync-docs", "pumuki sdd learn" or "pumuki sdd auto-sync".\n\n${HELP_TEXT}`);
698
729
  }
699
730
  if (arg.startsWith('--task=')) {
700
731
  if (sddCommand === 'sync-docs') {
@@ -713,7 +744,15 @@ export const parseLifecycleCliArgs = (argv: ReadonlyArray<string>): ParsedArgs =
713
744
  sddLearnTask = taskValue;
714
745
  continue;
715
746
  }
716
- throw new Error(`--task is only supported with "pumuki sdd sync-docs" or "pumuki sdd learn".\n\n${HELP_TEXT}`);
747
+ if (sddCommand === 'auto-sync') {
748
+ const taskValue = arg.slice('--task='.length).trim();
749
+ if (taskValue.length === 0) {
750
+ throw new Error(`Invalid --task value "${arg}".`);
751
+ }
752
+ sddAutoSyncTask = taskValue;
753
+ continue;
754
+ }
755
+ throw new Error(`--task is only supported with "pumuki sdd sync-docs", "pumuki sdd learn" or "pumuki sdd auto-sync".\n\n${HELP_TEXT}`);
717
756
  }
718
757
  if (arg.startsWith('--ttl-minutes=')) {
719
758
  if (sddCommand !== 'session') {
@@ -783,6 +822,26 @@ export const parseLifecycleCliArgs = (argv: ReadonlyArray<string>): ParsedArgs =
783
822
  ...(sddLearnTask ? { sddLearnTask } : {}),
784
823
  };
785
824
  }
825
+ if (sddCommand === 'auto-sync') {
826
+ if (sddSessionAction || sddChangeId || typeof sddTtlMinutes === 'number') {
827
+ throw new Error(
828
+ `"pumuki sdd auto-sync" only supports --change=<change-id> [--stage=PRE_WRITE|PRE_COMMIT|PRE_PUSH|CI] [--task=<task-id>] [--dry-run] [--json].\n\n${HELP_TEXT}`
829
+ );
830
+ }
831
+ if (!sddAutoSyncChange || sddAutoSyncChange.length === 0) {
832
+ throw new Error(`Missing --change=<change-id> for "pumuki sdd auto-sync".\n\n${HELP_TEXT}`);
833
+ }
834
+ return {
835
+ command: commandRaw,
836
+ purgeArtifacts: false,
837
+ json,
838
+ sddCommand,
839
+ sddAutoSyncDryRun,
840
+ sddAutoSyncChange,
841
+ ...(sddAutoSyncStage ? { sddAutoSyncStage } : {}),
842
+ ...(sddAutoSyncTask ? { sddAutoSyncTask } : {}),
843
+ };
844
+ }
786
845
 
787
846
  if (!sddSessionAction) {
788
847
  throw new Error(
@@ -922,6 +981,11 @@ const printDoctorReport = (
922
981
  writeInfo(
923
982
  `[pumuki] hook pre-push: ${report.hookStatus['pre-push'].managedBlockPresent ? 'managed' : 'missing'}`
924
983
  );
984
+ writeInfo(
985
+ `[pumuki] policy-as-code: PRE_COMMIT=${report.policyValidation.stages.PRE_COMMIT.validationCode ?? 'n/a'} strict=${report.policyValidation.stages.PRE_COMMIT.strict ? 'yes' : 'no'} ` +
986
+ `PRE_PUSH=${report.policyValidation.stages.PRE_PUSH.validationCode ?? 'n/a'} strict=${report.policyValidation.stages.PRE_PUSH.strict ? 'yes' : 'no'} ` +
987
+ `CI=${report.policyValidation.stages.CI.validationCode ?? 'n/a'} strict=${report.policyValidation.stages.CI.strict ? 'yes' : 'no'}`
988
+ );
925
989
 
926
990
  for (const issue of report.issues) {
927
991
  writeInfo(`[pumuki] ${issue.severity.toUpperCase()}: ${issue.message}`);
@@ -961,6 +1025,7 @@ const PRE_WRITE_INSTALL_REMEDIATION_COMMAND =
961
1025
  type PreWriteValidationEnvelope = {
962
1026
  sdd: ReturnType<typeof evaluateSddPolicy>;
963
1027
  ai_gate: ReturnType<typeof evaluateAiGate>;
1028
+ policy_validation: LifecyclePolicyValidationSnapshot;
964
1029
  automation: PreWriteAutomationTrace;
965
1030
  bootstrap: {
966
1031
  enabled: boolean;
@@ -1174,12 +1239,14 @@ const resolveAiGateViolationLocation = (code: string) => {
1174
1239
  const buildPreWriteValidationEnvelope = (
1175
1240
  result: ReturnType<typeof evaluateSddPolicy>,
1176
1241
  aiGate: ReturnType<typeof evaluateAiGate>,
1242
+ policyValidation: LifecyclePolicyValidationSnapshot,
1177
1243
  automation: PreWriteAutomationTrace,
1178
1244
  bootstrap: PreWriteOpenSpecBootstrapTrace,
1179
1245
  nextAction: PreWriteValidationEnvelope['next_action']
1180
1246
  ): PreWriteValidationEnvelope => ({
1181
1247
  sdd: result,
1182
1248
  ai_gate: aiGate,
1249
+ policy_validation: policyValidation,
1183
1250
  automation: {
1184
1251
  attempted: automation.attempted,
1185
1252
  actions: [...automation.actions],
@@ -1343,6 +1410,11 @@ export const runLifecycleCli = async (
1343
1410
  writeInfo(
1344
1411
  `[pumuki] tracked node_modules paths: ${status.trackedNodeModulesCount}`
1345
1412
  );
1413
+ writeInfo(
1414
+ `[pumuki] policy-as-code: PRE_COMMIT=${status.policyValidation.stages.PRE_COMMIT.validationCode ?? 'n/a'} strict=${status.policyValidation.stages.PRE_COMMIT.strict ? 'yes' : 'no'} ` +
1415
+ `PRE_PUSH=${status.policyValidation.stages.PRE_PUSH.validationCode ?? 'n/a'} strict=${status.policyValidation.stages.PRE_PUSH.strict ? 'yes' : 'no'} ` +
1416
+ `CI=${status.policyValidation.stages.CI.validationCode ?? 'n/a'} strict=${status.policyValidation.stages.CI.strict ? 'yes' : 'no'}`
1417
+ );
1346
1418
  if (remoteCiDiagnostics) {
1347
1419
  printRemoteCiDiagnostics(remoteCiDiagnostics);
1348
1420
  }
@@ -1601,6 +1673,7 @@ export const runLifecycleCli = async (
1601
1673
  let result = evaluateSddPolicy({
1602
1674
  stage: parsed.sddStage ?? 'PRE_COMMIT',
1603
1675
  });
1676
+ const policyValidation = readLifecyclePolicyValidationSnapshot(process.cwd());
1604
1677
  const preWriteAutoBootstrapEnabled = process.env.PUMUKI_PREWRITE_AUTO_BOOTSTRAP !== '0';
1605
1678
  const preWriteBootstrapTrace: PreWriteOpenSpecBootstrapTrace = {
1606
1679
  enabled: preWriteAutoBootstrapEnabled,
@@ -1672,11 +1745,15 @@ export const runLifecycleCli = async (
1672
1745
  ? buildPreWriteValidationEnvelope(
1673
1746
  result,
1674
1747
  aiGate,
1748
+ policyValidation,
1675
1749
  automationTrace,
1676
1750
  preWriteBootstrapTrace,
1677
1751
  nextAction
1678
1752
  )
1679
- : result,
1753
+ : {
1754
+ ...result,
1755
+ policy_validation: policyValidation,
1756
+ },
1680
1757
  null,
1681
1758
  2
1682
1759
  )
@@ -1685,6 +1762,11 @@ export const runLifecycleCli = async (
1685
1762
  writeInfo(
1686
1763
  `[pumuki][sdd] stage=${result.stage} allowed=${result.decision.allowed ? 'yes' : 'no'} code=${result.decision.code}`
1687
1764
  );
1765
+ writeInfo(
1766
+ `[pumuki][sdd] policy-as-code: PRE_COMMIT=${policyValidation.stages.PRE_COMMIT.validationCode ?? 'n/a'} strict=${policyValidation.stages.PRE_COMMIT.strict ? 'yes' : 'no'} ` +
1767
+ `PRE_PUSH=${policyValidation.stages.PRE_PUSH.validationCode ?? 'n/a'} strict=${policyValidation.stages.PRE_PUSH.strict ? 'yes' : 'no'} ` +
1768
+ `CI=${policyValidation.stages.CI.validationCode ?? 'n/a'} strict=${policyValidation.stages.CI.strict ? 'yes' : 'no'}`
1769
+ );
1688
1770
  writeInfo(
1689
1771
  withOptionalLocation(
1690
1772
  `[pumuki][sdd] ${result.decision.message}`,
@@ -1824,6 +1906,31 @@ export const runLifecycleCli = async (
1824
1906
  }
1825
1907
  return 0;
1826
1908
  }
1909
+ if (parsed.sddCommand === 'auto-sync') {
1910
+ const autoSyncResult = runSddAutoSync({
1911
+ repoRoot: process.cwd(),
1912
+ dryRun: parsed.sddAutoSyncDryRun === true,
1913
+ change: parsed.sddAutoSyncChange,
1914
+ stage: parsed.sddAutoSyncStage,
1915
+ task: parsed.sddAutoSyncTask,
1916
+ });
1917
+ if (parsed.json) {
1918
+ writeInfo(JSON.stringify(autoSyncResult, null, 2));
1919
+ } else {
1920
+ writeInfo(
1921
+ `[pumuki][sdd] auto-sync dry_run=${autoSyncResult.dryRun ? 'yes' : 'no'} change=${autoSyncResult.context.change} stage=${autoSyncResult.context.stage ?? 'none'} task=${autoSyncResult.context.task ?? 'none'} updated=${autoSyncResult.syncDocs.updated ? 'yes' : 'no'} files=${autoSyncResult.syncDocs.files.length} learning_written=${autoSyncResult.learning.written ? 'yes' : 'no'}`
1922
+ );
1923
+ writeInfo(
1924
+ `[pumuki][sdd] learning_path=${autoSyncResult.learning.path} digest=${autoSyncResult.learning.digest}`
1925
+ );
1926
+ for (const file of autoSyncResult.syncDocs.files) {
1927
+ writeInfo(
1928
+ `[pumuki][sdd] file=${file.path} updated=${file.updated ? 'yes' : 'no'} before=${file.beforeDigest} after=${file.afterDigest}`
1929
+ );
1930
+ }
1931
+ }
1932
+ return 0;
1933
+ }
1827
1934
  return 0;
1828
1935
  }
1829
1936
  case 'adapter': {
@@ -5,6 +5,10 @@ import { resolvePolicyForStage } from '../gate/stagePolicies';
5
5
  import { getPumukiHooksStatus } from './hookManager';
6
6
  import { LifecycleGitService, type ILifecycleGitService } from './gitService';
7
7
  import { getCurrentPumukiVersion } from './packageInfo';
8
+ import {
9
+ readLifecyclePolicyValidationSnapshot,
10
+ type LifecyclePolicyValidationSnapshot,
11
+ } from './policyValidationSnapshot';
8
12
  import { readLifecycleState, type LifecycleState } from './state';
9
13
  import {
10
14
  detectOpenSpecInstallation,
@@ -71,6 +75,7 @@ export type LifecycleDoctorReport = {
71
75
  lifecycleState: LifecycleState;
72
76
  trackedNodeModulesPaths: ReadonlyArray<string>;
73
77
  hookStatus: ReturnType<typeof getPumukiHooksStatus>;
78
+ policyValidation: LifecyclePolicyValidationSnapshot;
74
79
  issues: ReadonlyArray<DoctorIssue>;
75
80
  deep?: DoctorDeepReport;
76
81
  };
@@ -653,6 +658,7 @@ export const runLifecycleDoctor = (params?: {
653
658
  lifecycleState,
654
659
  trackedNodeModulesPaths,
655
660
  hookStatus,
661
+ policyValidation: readLifecyclePolicyValidationSnapshot(repoRoot),
656
662
  issues,
657
663
  deep,
658
664
  };
@@ -0,0 +1,51 @@
1
+ import type { SkillsStage } from '../config/skillsLock';
2
+ import {
3
+ resolvePolicyForStage,
4
+ type ResolvedStagePolicy,
5
+ } from '../gate/stagePolicies';
6
+
7
+ export type LifecyclePolicyValidationStageSnapshot = {
8
+ source: ResolvedStagePolicy['trace']['source'];
9
+ bundle: string;
10
+ hash: string;
11
+ version: string | null;
12
+ signature: string | null;
13
+ policySource: string | null;
14
+ validationStatus: NonNullable<ResolvedStagePolicy['trace']['validation']>['status'] | null;
15
+ validationCode: NonNullable<ResolvedStagePolicy['trace']['validation']>['code'] | null;
16
+ strict: boolean;
17
+ };
18
+
19
+ export type LifecyclePolicyValidationSnapshot = {
20
+ stages: Record<SkillsStage, LifecyclePolicyValidationStageSnapshot>;
21
+ };
22
+
23
+ const POLICY_STAGES: ReadonlyArray<SkillsStage> = ['PRE_COMMIT', 'PRE_PUSH', 'CI'];
24
+
25
+ const toStageSnapshot = (
26
+ resolved: ResolvedStagePolicy
27
+ ): LifecyclePolicyValidationStageSnapshot => {
28
+ return {
29
+ source: resolved.trace.source,
30
+ bundle: resolved.trace.bundle,
31
+ hash: resolved.trace.hash,
32
+ version: resolved.trace.version ?? null,
33
+ signature: resolved.trace.signature ?? null,
34
+ policySource: resolved.trace.policySource ?? null,
35
+ validationStatus: resolved.trace.validation?.status ?? null,
36
+ validationCode: resolved.trace.validation?.code ?? null,
37
+ strict: resolved.trace.validation?.strict ?? false,
38
+ };
39
+ };
40
+
41
+ export const readLifecyclePolicyValidationSnapshot = (
42
+ repoRoot: string
43
+ ): LifecyclePolicyValidationSnapshot => {
44
+ const resolvedByStage = Object.fromEntries(
45
+ POLICY_STAGES.map((stage) => [stage, toStageSnapshot(resolvePolicyForStage(stage, repoRoot))])
46
+ ) as Record<SkillsStage, LifecyclePolicyValidationStageSnapshot>;
47
+
48
+ return {
49
+ stages: resolvedByStage,
50
+ };
51
+ };
@@ -1,6 +1,10 @@
1
1
  import { getPumukiHooksStatus } from './hookManager';
2
2
  import { LifecycleGitService, type ILifecycleGitService } from './gitService';
3
3
  import { getCurrentPumukiVersion } from './packageInfo';
4
+ import {
5
+ readLifecyclePolicyValidationSnapshot,
6
+ type LifecyclePolicyValidationSnapshot,
7
+ } from './policyValidationSnapshot';
4
8
  import { readLifecycleState, type LifecycleState } from './state';
5
9
 
6
10
  export type LifecycleStatus = {
@@ -9,6 +13,7 @@ export type LifecycleStatus = {
9
13
  lifecycleState: LifecycleState;
10
14
  hookStatus: ReturnType<typeof getPumukiHooksStatus>;
11
15
  trackedNodeModulesCount: number;
16
+ policyValidation: LifecyclePolicyValidationSnapshot;
12
17
  };
13
18
 
14
19
  export const readLifecycleStatus = (params?: {
@@ -26,5 +31,6 @@ export const readLifecycleStatus = (params?: {
26
31
  lifecycleState: readLifecycleState(git, repoRoot),
27
32
  hookStatus: getPumukiHooksStatus(repoRoot),
28
33
  trackedNodeModulesCount,
34
+ policyValidation: readLifecyclePolicyValidationSnapshot(repoRoot),
29
35
  };
30
36
  };
@@ -9,4 +9,4 @@ export type {
9
9
  } from './types';
10
10
  export { evaluateSddPolicy, readSddStatus } from './policy';
11
11
  export { closeSddSession, openSddSession, readSddSession, refreshSddSession } from './sessionStore';
12
- export { runSddLearn, runSddSyncDocs } from './syncDocs';
12
+ export { runSddAutoSync, runSddLearn, runSddSyncDocs } from './syncDocs';
@@ -85,6 +85,22 @@ export type SddLearnResult = {
85
85
  learning: NonNullable<SddSyncDocsResult['learning']>;
86
86
  };
87
87
 
88
+ export type SddAutoSyncResult = {
89
+ command: 'pumuki sdd auto-sync';
90
+ dryRun: boolean;
91
+ repoRoot: string;
92
+ context: {
93
+ change: string;
94
+ stage: SddStage | null;
95
+ task: string | null;
96
+ };
97
+ syncDocs: {
98
+ updated: boolean;
99
+ files: ReadonlyArray<SddSyncDocsFileResult>;
100
+ };
101
+ learning: NonNullable<SddSyncDocsResult['learning']>;
102
+ };
103
+
88
104
  const normalizeSectionBody = (value: string): string => value.trim().replace(/\r\n/g, '\n');
89
105
 
90
106
  const computeDigest = (value: string): string =>
@@ -433,3 +449,50 @@ export const runSddLearn = (params?: {
433
449
  learning: result.learning,
434
450
  };
435
451
  };
452
+
453
+ export const runSddAutoSync = (params?: {
454
+ repoRoot?: string;
455
+ dryRun?: boolean;
456
+ change?: string;
457
+ stage?: SddStage;
458
+ task?: string;
459
+ now?: () => Date;
460
+ evidenceReader?: (repoRoot: string) => EvidenceReadResult;
461
+ targets?: ReadonlyArray<SddSyncDocsTarget>;
462
+ }): SddAutoSyncResult => {
463
+ const change = params?.change?.trim();
464
+ if (!change) {
465
+ throw new Error('[pumuki][sdd] auto-sync requires --change=<change-id>.');
466
+ }
467
+
468
+ const syncResult = runSddSyncDocs({
469
+ repoRoot: params?.repoRoot,
470
+ dryRun: params?.dryRun,
471
+ change,
472
+ stage: params?.stage,
473
+ task: params?.task,
474
+ now: params?.now,
475
+ evidenceReader: params?.evidenceReader,
476
+ targets: params?.targets,
477
+ });
478
+
479
+ if (!syncResult.learning) {
480
+ throw new Error('[pumuki][sdd] auto-sync could not generate learning artifact.');
481
+ }
482
+
483
+ return {
484
+ command: 'pumuki sdd auto-sync',
485
+ dryRun: syncResult.dryRun,
486
+ repoRoot: syncResult.repoRoot,
487
+ context: {
488
+ change,
489
+ stage: syncResult.context.stage,
490
+ task: syncResult.context.task,
491
+ },
492
+ syncDocs: {
493
+ updated: syncResult.updated,
494
+ files: syncResult.files,
495
+ },
496
+ learning: syncResult.learning,
497
+ };
498
+ };
@@ -25,7 +25,7 @@ type PolicyTrace = ResolvedStagePolicy['trace'] & {
25
25
  signature?: string;
26
26
  policySource?: string;
27
27
  validation?: {
28
- status: 'valid' | 'invalid' | 'expired' | 'unknown-source';
28
+ status: 'valid' | 'invalid' | 'expired' | 'unknown-source' | 'unsigned';
29
29
  code: string;
30
30
  };
31
31
  degraded?: {
@@ -189,7 +189,7 @@ export type GateTelemetryEventV1 = {
189
189
  version?: string;
190
190
  signature?: string;
191
191
  policy_source?: string;
192
- validation_status?: 'valid' | 'invalid' | 'expired' | 'unknown-source';
192
+ validation_status?: 'valid' | 'invalid' | 'expired' | 'unknown-source' | 'unsigned';
193
193
  validation_code?: string;
194
194
  degraded_mode_enabled?: boolean;
195
195
  degraded_mode_action?: 'allow' | 'block';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pumuki",
3
- "version": "6.3.35",
3
+ "version": "6.3.37",
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": {