skills: 9 skill-uri de agent ROA (COMUN\skills) + legare si rutare

- leaga_skills.ps1: expune skill-urile in ~/.claude/skills prin symlink (citit si de Claude Code, si de opencode), fara copiere
- rutare zona->skill in reguli_lucru pct. 7, README, AGENTS.md; skill-ul devine proprietarul procedurii, docurile raman referinta conditionata
- rec_skills.md: decizia, inventarul si cum se adauga un skill nou
This commit is contained in:
2026-09-16 11:54:37 +03:00
parent 59c0c7be31
commit d64124386c
13 changed files with 1043 additions and 37 deletions

View File

@@ -14,30 +14,30 @@ atingi zona X, citeste fisierul Y"). Restul se deschid doar la declansator.
| `reguli_lucru.md` | intotdeauna, la inceput de sesiune | | `reguli_lucru.md` | intotdeauna, la inceput de sesiune |
| `orchestrare-subagenti.md` | cand delegi, cand verifici ce intoarce un subagent, la predarea contextului | | `orchestrare-subagenti.md` | cand delegi, cand verifici ce intoarce un subagent, la predarea contextului |
| `monitorizare-context.md` | instalarea hook-urilor de prag de context si de revizie a docs | | `monitorizare-context.md` | instalarea hook-urilor de prag de context si de revizie a docs |
| `fluxul_svn_git.md` | orice commit / `svn update` / `roa_sync`; ce e interzis pe `main` in COMUN | | `fluxul_svn_git.md` | orice commit / `svn update` / `roa_sync` -> skill `roa-git-svn-commit` |
| `scripturi-uzuale.md` | exista deja un script pentru sarcina asta? (roa_sync, curatenie, livrare, diagnostice) | | `scripturi-uzuale.md` | exista deja un script pentru sarcina asta? (roa_sync, curatenie, livrare, diagnostice) |
| **VFP** | | | **VFP** | |
| `cautare_vcx_vct.md` | cauti cod in `.vcx`/`.scx`/`.frx`/`.mnx` | | `cautare_vcx_vct.md` | cauti cod in `.vcx`/`.scx`/`.frx`/`.mnx` -> skill `roa-vfp-text-edit` |
| `flux-editare-vfp-text.md` | editezi `.vc2`/`.sc2` + write-back; **invariantele si `*p:`/`*m:`** | | `flux-editare-vfp-text.md` | editezi `.vc2`/`.sc2` + write-back; invariantele si `*p:`/`*m:` -> skill `roa-vfp-text-edit` |
| `conventie_encoding_cp1252.md` | fisierul editat are diacritice (cp1250, nu cp1252) | | `conventie_encoding_cp1252.md` | fisierul editat are diacritice (cp1250, nu cp1252) -> skill `roa-vfp-text-edit` |
| `inventar-comun.md` | inainte de cod nou: exista deja o functie/clasa comuna? | | `inventar-comun.md` | inainte de cod nou: exista deja o functie/clasa comuna? |
| `conventie_ux_formulare.md` | adaugi/rearanjezi controale pe formular | | `conventie_ux_formulare.md` | adaugi/rearanjezi controale pe formular |
| `capcana_grid_controlsource.md` | grid needitabil pe formular cu 2+ grid-uri | | `capcana_grid_controlsource.md` | grid needitabil pe formular cu 2+ grid-uri |
| `capcana_grid_preferinte_utilizator.md` | adaugi o coloana intr-un grid deja livrat | | `capcana_grid_preferinte_utilizator.md` | adaugi o coloana intr-un grid deja livrat |
| `conventie_go_recno.md` | `GO` pe un `Recno()` capturat | | `conventie_go_recno.md` | `GO` pe un `Recno()` capturat -> skill `roa-vfp-go-recno` |
| `conventie_null_vfp.md` | garzi pe valori NULL din Oracle | | `conventie_null_vfp.md` | garzi pe valori NULL din Oracle -> skill `roa-vfp-null-guards` |
| `conventie_goexecutor_alter_table.md` | `ALTER TABLE` pe cursorul de la `oExecute()` | | `conventie_goexecutor_alter_table.md` | `ALTER TABLE` pe cursorul de la `oExecute()` |
| **Testare** | | | **Testare** | |
| `depanare_testare_vfp.md` | rulare headless, depanare bug runtime, capcane VFP | | `depanare_testare_vfp.md` | rulare headless, depanare bug runtime, capcane VFP -> skill `roa-vfp-headless-test` |
| `testare-ui-vfp.md` | test cu formular real + screenshots (harness UI) | | `testare-ui-vfp.md` | test cu formular real + screenshots (harness UI) -> skill `roa-vfp-headless-test` |
| **Oracle** | | | **Oracle** | |
| `conventie_mediu_oracle.md` | te conectezi la dev/test, alegi schema, rulezi un script de pachet | | `conventie_mediu_oracle.md` | te conectezi la dev/test, alegi schema, rulezi un script de pachet |
| `conexiuni-tunel-ssh-odbc.md` | ajungi la un Oracle de PRODUCTIE prin tunel SSH (Bitvise) + alias TNS + DSN ODBC | | `conexiuni-tunel-ssh-odbc.md` | ajungi la un Oracle de PRODUCTIE prin tunel SSH (Bitvise) + alias TNS + DSN ODBC -> skill `roa-oracle-production-access` |
| `scripturi-migrare-db.md` | modifici schema (tabele/view-uri/pachete); compatibilitate 10.2 | | `scripturi-migrare-db.md` | modifici schema (tabele/view-uri/pachete); compatibilitate 10.2 -> skill `roa-oracle-migration` |
| `publicare-scripturi-db.md` | publici scripturile catre clienti | | `publicare-scripturi-db.md` | publici scripturile catre clienti -> skill `roa-oracle-migration` |
| `oracle_export.md` | reexporti sursa unui pachet PL/SQL | | `oracle_export.md` | reexporti sursa unui pachet PL/SQL -> skill `roa-oracle-migration` |
| `depanare-pack-update.md` | actualizarea ROA nu se finalizeaza la un client | | `depanare-pack-update.md` | actualizarea ROA nu se finalizeaza la un client -> skill `roa-oracle-diagnostic` |
| `depanare-spatiu-oracle.md` | spatiu plin la un client | | `depanare-spatiu-oracle.md` | spatiu plin la un client -> skill `roa-oracle-diagnostic` |
| **Domeniu** | | | **Domeniu** | |
| `flux-modificare-stergere-nota-jurnal.md` | modificare/stergere nota; ce face `PACK_CONTAFIN` | | `flux-modificare-stergere-nota-jurnal.md` | modificare/stergere nota; ce face `PACK_CONTAFIN` |
| `tipuri_documente_facturare.md` | `VANZARI.TIP` si seturile de contare `ID_SET` | | `tipuri_documente_facturare.md` | `VANZARI.TIP` si seturile de contare `ID_SET` |

View File

@@ -0,0 +1,59 @@
# rec_skills - skill-uri de agent pentru suita ROA
Data: 2026-09-16. Status: aplicat (9 skill-uri create, rutare + backstop puse in docs).
## Decizie
- Skill-ul este **proprietarul procedurii**; docurile din `COMUN\docs` raman **referinta
conditionata**, deschisa doar cand o ramura cere detaliu. Motiv: sa nu se incarce contextul de
doua ori si sa nu existe doi proprietari pentru aceeasi regula. (Suprapunerea masurata inainte
de unificare: 18-42% din liniile corpului unui skill erau identice cu linii din docuri.)
- Fiecare SKILL.md: frontmatter doar `name` + `description` (comun ambelor host-uri), corp
auto-suficient cu secventele `Cand se foloseste / Pasi (sau Regula) / Capcane / Criterii de
acceptare si dovada / Scripturi / Referinta (doar la nevoie)`, cel mult 1 pointer conditio nat la
un doc.
- Skill-urile traiesc in `COMUN\skills\<nume>\SKILL.md`, versionate in `comun.git`.
- Sunt expuse host-urilor prin symlink in `%USERPROFILE%\.claude\skills\` - director citit si de
Claude Code, si de opencode - fara copiere: `COMUN\utile\leaga_skills.ps1` (idempotent, `-DryRun`).
- Declansarea automata vine din `description` (ambele host-uri); "de fiecare data" vine din
**backstop-ul always-on**: `reguli_lucru.md` pct. 7 (citit la fiecare sesiune) + `AGENTS.md`
(pentru opencode, care nu citeste `CLAUDE.md`) + ancora in `CLAUDE.md`.
## Inventar
| Skill | Deschide cand (zonele) |
|---|---|
| `roa-vfp-text-edit` | editare `.vcx/.scx` pe text (`.vc2/.sc2`), cautare in binare, encoding cp1250, write-back |
| `roa-vfp-headless-test` | rulare/depanare teste VFP headless, harness UI + screenshots |
| `roa-git-svn-commit` | commit / `svn update` / `roa_sync` in COMUN |
| `roa-oracle-migration` | scriere + publicare scripturi de migrare, re-export pachet PL/SQL |
| `roa-oracle-diagnostic` | update ROA blocat la client, tablespace/disc plin |
| `roa-oracle-production-access` | acces Oracle de productie (tunel SSH + TNS + DSN ODBC) |
| `roa-vfp-null-guards` | garzi pe valori NULL din Oracle in VFP |
| `roa-vfp-go-recno` | `GO` pe un `Recno()` capturat sau primit ca parametru |
| `roa-oracle-sqlexec-no-data` | procedura PL/SQL cu `SELECT ... INTO` apelata din VFP (randuri pierdute tacut) |
## Cum se adauga un skill nou
1. Creezi `COMUN\skills\<nume>\SKILL.md` (contractul de mai sus; `name` = numele folderului,
lowercase-cu-cratima).
2. Rulezi `COMUN\utile\leaga_skills.ps1` (creeaza symlink-ul in `%USERPROFILE%\.claude\skills\`).
3. Adaugi o linie de rutare in `reguli_lucru.md` pct. 7 si in `AGENTS.md` (`README.md` pentru
index). Nu duplici regula din docs: skill-ul o detine.
4. Verifici cu cele doua asertiuni: (a) nicio linie din `CLAUDE.md`/`AGENTS.md`/`reguli_lucru.md`/
`README.md` nu ruteaza o zona acoperita catre un doc; (b) fiecare skill are cel mult 1 pointer
conditio nat la un doc.
## Verificari la data aplicarii
- 9/9 skill-uri: ASCII pur, fara BOM, `name` = folder, descriere <= 1024, exact 1 bullet sub
`## Referinta (doar la nevoie)`.
- 0 rute reziduale zona -> doc in `CLAUDE.md`, `AGENTS.md`, `reguli_lucru.md`, `README.md`.
- `leaga_skills.ps1`: 4 create apoi idempotent (0 create, 9 ok); director real cu acelasi nume
niciodata sters (nici cu `-Force`).
## Neaplicat / de facut manual
- Commit: git pe branch (`claude/...`), fara push; `svn commit` + `roa_sync` + merge raman la
Marius (fluxul SVN+git din `fluxul_svn_git.md`).
- Pe celelalte calculatoare: `git pull` in `COMUN\` + `leaga_skills.ps1` (o data).

View File

@@ -96,7 +96,7 @@
garda fara caz in datele de test nu e garda moarta (regula 6); `/ponytail-audit` pe tot garda fara caz in datele de test nu e garda moarta (regula 6); `/ponytail-audit` pe tot
arborele nu se ruleaza aici. arborele nu se ruleaza aici.
4. Testare headless (fara IDE): `vfp9.exe -A -T "<script.prg>" <param>` din PowerShell. 4. Testare headless (fara IDE): `vfp9.exe -A -T "<script.prg>" <param>` din PowerShell.
Mediu, sabloane, capcane, depanare: `depanare_testare_vfp.md`. Mediu, sabloane, capcane, depanare: skill roa-vfp-headless-test (headless si UI; criteriile lui blocheaza "gata").
Rularile intermediare acopera doar familiile de teste atinse de modificarea curenta; suita Rularile intermediare acopera doar familiile de teste atinse de modificarea curenta; suita
completa, inclusiv testele de garda pe zonele vecine, se ruleaza doar la rularea finala — completa, inclusiv testele de garda pe zonele vecine, se ruleaza doar la rularea finala —
garzile exista fiindca schimbarile cad de regula in cod partajat (`COMUN`), vizibil in toate garzile exista fiindca schimbarile cad de regula in cod partajat (`COMUN`), vizibil in toate
@@ -107,41 +107,32 @@
**0,5s** intre teste, nu implicitul de 12 minute / 3s, rezervat rularii finale unde chiar **0,5s** intre teste, nu implicitul de 12 minute / 3s, rezervat rularii finale unde chiar
conteaza sa distingi "atarnat" de "lent". Tinta: o rulare intermediara sta in secunde-minute, conteaza sa distingi "atarnat" de "lent". Tinta: o rulare intermediara sta in secunde-minute,
nu ore — daca depaseste, lista e prea larga sau timeout-ul prea mare. nu ore — daca depaseste, lista e prea larga sau timeout-ul prea mare.
5. Editare .vcx/.scx pe text (cache, cp1250 byte-safe, fidelity-check): `flux-editare-vfp-text.md`. 5. Editare `.vcx`/`.scx` pe text, cautare in binare: skill roa-vfp-text-edit (cache, cp1250
Cautare in binare: `cautare_vcx_vct.md`. Versiunile `.??2` sunt instantanee, pot fi mai vechi byte-safe, fidelity-check si membrii noi sunt in procedura lui). Versiunile `.??2` sunt
decat binarul — verifica mtime inainte de concluzii; continutul real e in `.PJX`. instantanee, pot fi mai vechi decat binarul - verifica mtime inainte de concluzii; continutul
real e in `.PJX`.
6. Delegare: implementarile, testele, verificarile si cercetarile se dau unor subagenti Sonnet in 6. Delegare: implementarile, testele, verificarile si cercetarile se dau unor subagenti Sonnet in
background (lane-uri paralele); sesiunea principala doar orchestreaza si verifica, ca sa nu i background (lane-uri paralele); sesiunea principala doar orchestreaza si verifica, ca sa nu i
se umple contextul. Misiuni lungi: subagenti proaspeti per sarcina (max ~200-250k se umple contextul. Misiuni lungi: subagenti proaspeti per sarcina (max ~200-250k
tokens/subagent), nu unul singur cu context acumulat; handoff compact pe disc. Verificarea a tokens/subagent), nu unul singur cu context acumulat; handoff compact pe disc. Verificarea a
ce se intoarce si modelul de lucru (backlog de story-uri mici): `orchestrare-subagenti.md`. ce se intoarce si modelul de lucru (backlog de story-uri mici): `orchestrare-subagenti.md`.
7. Conventii obligatorii, de citit cand atingi zona respectiva: 7. Conventii obligatorii, de citit cand atingi zona respectiva:
- editezi `.vc2`/`.sc2` sau `.prg` cu diacritice (ex. `programe\ocautare.prg`, unde `s`-urile cu - editezi `.vc2`/`.sc2` sau `.prg` cu diacritice -> skill roa-vfp-text-edit (el detine procedura; criteriile lui blocheaza "gata");
virgula din `masina`/`si contul`/`transa` sunt octetul `0xBA`) -> fisierele sunt **cp1250** - adaugi o PROPRIETATE sau o METODA noua intr-o clasa `.vc2`/`.sc2` -> skill roa-vfp-text-edit (membrul nou si `*p:`/`*m:` sunt in procedura lui; criteriile lui blocheaza "gata");
desi antetul FoxBin2Prg declara `CPID="1252"`; Edit/Write corup TOT fisierul (nu doar linia
atinsa), ireversibil, la fiecare scriere - scrie continut nou strict ASCII. Cens obligatoriu
**inainte SI dupa** fiecare editare (la stricare CRESTE, nu scade):
`od -An -tx1 <fisier> | tr ' ' '\n' | grep -v '^$' | awk '$1>"7f"' | sort | uniq -c`. Tabelul
complet de octeti, sensul censului la stricare si procedura de recuperare din git/svn ->
`conventie_encoding_cp1252.md`;
- adaugi o PROPRIETATE sau o METODA noua intr-o clasa `.vc2`/`.sc2` -> `flux-editare-vfp-text.md`,
sectiunea "Membri noi de clasa": fara intrarea `*p:`/`*m:` in `*<DefinedPropArrayMethod>`
membrul trece fidelity-check-ul, dar VFP nu-l vede si prima salvare din IDE il sterge tacut;
- adaugi/rearanjezi controale pe formulare sau coloane in grid -> `conventie_ux_formulare.md`; - adaugi/rearanjezi controale pe formulare sau coloane in grid -> `conventie_ux_formulare.md`;
- grid needitabil pe formular cu 2+ grid-uri -> `capcana_grid_controlsource.md`; - grid needitabil pe formular cu 2+ grid-uri -> `capcana_grid_controlsource.md`;
- adaugi o coloana intr-un grid deja livrat (ordinea din clasa e neutralizata de - adaugi o coloana intr-un grid deja livrat (ordinea din clasa e neutralizata de
preferintele salvate per utilizator) -> `capcana_grid_preferinte_utilizator.md`; preferintele salvate per utilizator) -> `capcana_grid_preferinte_utilizator.md`;
- `GO` pe un `Recno()` capturat/primit ca parametru -> `conventie_go_recno.md`; - `GO` pe un `Recno()` capturat/primit ca parametru -> skill roa-vfp-go-recno (el detine procedura; criteriile lui blocheaza "gata");
- `ALTER TABLE` pe cursorul intors de `goExecutor.oExecute()` -> `conventie_goexecutor_alter_table.md`; - `ALTER TABLE` pe cursorul intors de `goExecutor.oExecute()` -> `conventie_goexecutor_alter_table.md`;
- testare UI -> `testare-ui-vfp.md`; - testare UI -> skill roa-vfp-headless-test (acopera si testul headless; criteriile lui blocheaza "gata");
- te conectezi la Oracle pe dev/test, alegi schema sau rulezi un script de pachet -> - te conectezi la Oracle pe dev/test, alegi schema sau rulezi un script de pachet ->
`conventie_mediu_oracle.md`; `conventie_mediu_oracle.md`;
- garzi pe valori NULL din Oracle -> `conventie_null_vfp.md`; - garzi pe valori NULL din Oracle -> skill roa-vfp-null-guards (el detine procedura; criteriile lui blocheaza "gata");
- procedura PL/SQL cu `SELECT ... INTO` chemata din VFP (linii pierdute tacut) -> - procedura PL/SQL cu `SELECT ... INTO` chemata din VFP (linii pierdute tacut) -> skill roa-oracle-sqlexec-no-data (el detine procedura; criteriile lui blocheaza "gata");
`capcana_sqlexec_no_data_found.md`; - export date din Oracle -> skill roa-oracle-migration (reexportul sursei de pachet e in procedura lui; criteriile lui blocheaza "gata");
- export date din Oracle -> `oracle_export.md`; - actualizarea ROA nu se finalizeaza (job update, PACK_UPDATE) -> skill roa-oracle-diagnostic (acopera si spatiul plin; criteriile lui blocheaza "gata");
- actualizarea ROA nu se finalizeaza (job update, PACK_UPDATE) -> `depanare-pack-update.md`; - modifici schema Oracle (tabele/view-uri/pachete) -> skill roa-oracle-migration (scriere + publicare scripturi; criteriile lui blocheaza "gata").
- modifici schema Oracle (tabele/view-uri/pachete) -> `scripturi-migrare-db.md`.
8. Eroare raportata de utilizator: nu analiza statica indelungata — scrie intai un test care o 8. Eroare raportata de utilizator: nu analiza statica indelungata — scrie intai un test care o
reproduce pe fluxul real; el da cauza si confirma remedierea. Analiza statica doar cat sa stii reproduce pe fluxul real; el da cauza si confirma remedierea. Analiza statica doar cat sa stii
ce sa pui in test. ce sa pui in test.

View File

@@ -0,0 +1,100 @@
---
name: roa-git-svn-commit
description: Fluxul de commit si sincronizare pentru biblioteca partajata COMUN (SVN = sursa de adevar, git = oglinda), cu arbore de decizie pentru roa_sync, write-back, aprobare explicita si svn commit tintit, ca sa nu pierzi munca necomisa. Foloseste-l OBLIGATORIU cand utilizatorul spune "am terminat, dau commit", "vreau sa comit in COMUN", "comit ce am lucrat", "sincronizeaza cu SVN", "adu ce au facut altii", "ruleaza roa_sync", "de ce nu merge git pull in COMUN" sau cere orice commit/sincronizare care atinge COMUN. Regula centrala: pe main din COMUN scrie DOAR roa_sync, commit-urile se fac tintit pe fisierele aprobate (niciodata pe tot COMUN), iar git pull in COMUN este interzis. Nu improviza ordinea pasilor si nu rula roa_sync pe un tree murdar: distruge munca necomisa.
---
# Commit si sincronizare in COMUN (SVN + git)
SVN = sursa de adevar. git = oglinda. Pe `main` din COMUN scrie **doar** `roa_sync`. Comenzile le
rulezi tu; utilizatorul doar aproba - dar nimic nu se comite fara un "da" explicit de la el.
## Cand se foloseste
- "am terminat, dau commit", "vreau sa comit in COMUN", "comit ce am lucrat";
- "sincronizeaza cu SVN", "adu ce au facut altii", "ruleaza roa_sync", "de ce am conflicte";
- intrarea intr-o sesiune de lucru in proiect (sync de inceput).
Nu se aplica altui repo decat COMUN + proiectul care il contine. Pentru editarea codului VFP
(`.vcx`/`.scx` prin `.vc2`/`.sc2`) vezi skill-ul `roa-vfp-text-edit`.
## Pasi
Arbore de decizie - opreste-te la primul caz care se potriveste.
1. **Intri in proiect** -> rulezi `roa_sync.bat`, fara sa intrebi, DOAR daca `svn status` si
`git status` sunt curate. Daca nu sunt, intrebi intai (pot fi modificari nefinalizate ale
altcuiva).
2. **Ai editat `.prg` / `.md` / `.ps1` / `.sql`** (proiect sau COMUN) -> antetul la zi, testul
rulat, diff-ul scris ca fisier in `docs\`.
3. **Ai editat `.vc2` / `.sc2`** -> `txt2vcx.ps1` IMEDIAT, apoi verifici pe binar (reconversie
intr-un cache temporar + comparatie; mtime nu dovedeste nimic). Text fara write-back =
modificarea nu exista. Abia apoi antet / test / diff.
4. **Ceri aprobarea, explicit si complet**: lista fisierelor de comis + mesajul propus. Nu
presupune "da" din tacere si nu amana pentru "mai tarziu" - el nu tine minte pasul, tu il tii.
5. **A zis "da"** -> rulezi tu `svn commit` **tintit**, doar fisierele aprobate: `.prg` sau
`.scx` + `.sct`, niciodata `.sc2`. Niciodata tot COMUN (intra zgomotul de compilare VFP).
6. **Imediat dupa** -> `roa_sync.bat`. Raportezi revizia SVN si ce a intrat pe `main`.
7. **A zis "nu" / cere modificari** -> nu comiti, nu insisti, nu rulezi `roa_sync`. Lucrarea
ramane necomisa pe disc si o raportezi asa la predarea contextului.
### Cazuri
- **Vrei sa-ti tii munca in git** -> branch. In repo-ul proiectului: liber. In COMUN:
`claude/<subiect>`, commit doar acolo, niciodata pe `main`.
- **Ai terminat branch-ul** -> proiect: merge in `main`, normal. COMUN: **fara merge**; dupa
`svn commit` continutul ajunge pe `main` ca `sync SVN rN`, iar branch-ul se sterge (doar daca
`git diff main..branch` e gol).
- **Esti pe branch in COMUN si te bate gandul la `roa_sync`** -> nu. `roa_sync` se ruleaza in
exact doua momente: la intrarea in proiect si imediat dupa `svn commit`. Pe branch cu tree
curat face `checkout main` fara sa intrebe (binarele raman de pe branch); pe branch murdar
sare peste, dar `svn update` de la pasul 1 ruleaza oricum si poate aduce binare peste ce ai
pe disc.
- **Vrei `svn update`** -> doar din stare curata, niciodata in mijlocul lucrului. Daca ai text
`.??2` editat fara write-back, fa intai write-back-ul.
- **`svn update` a dat conflict** -> opreste-te si raporteaza; binarele nu se rezolva manual.
- **Dupa `svn update` git arata fisiere modificate** -> e oglinda ramasa in urma, nu munca ta;
nu le comite de mana, le ia `roa_sync` intr-un `sync SVN rN`.
- **Vrei sa aduci ce au facut altii** -> `roa_sync.bat`. **Niciodata `git pull`**: git poarta
doar textele, nu binarele; un pull da `.sc2` nou peste `.scx/.sct` vechi, VFP compileaza
binarul, iar urmatorul `git_sync` sterge modificarea.
- **Copie de COMUN ramasa in urma** -> se aliniaza singura la primul `roa_sync.bat` rulat acolo.
- **Predai contextul** -> raporteaza explicit daca ai lasat text `.??2` editat fara write-back
(stare periculoasa).
## Interzis
- `git commit` / `git push` / `git merge` pe `main` in COMUN.
- `git pull` in COMUN - aduce textele fara binare; se foloseste `roa_sync.bat`.
- `git commit` pe tot COMUN sau pe `.sc2` - doar tintit, pe fisierele aprobate.
- Copierea unui fisier dintr-o copie de COMUN in alta - `svn update` il duce; copierea =
intrare dubla.
- Write-back pe `.fr2` / `.mn2` / `.lb2` / `.pj2` - nesuportat; acolo se modifica in IDE.
- Commit fara un "da" explicit de la utilizator.
## Criterii de acceptare si dovada
Fara dovada da/nu de mai jos, commit-ul/sincronizarea NU se raporteaza ca terminata. "Arata
bine" nu e dovada.
1. Inainte de commit: `git status --porcelain` contine EXACT fisierele aprobate; nimic
neasteptat stagiat.
2. Dupa `svn commit`: `svn status` curat pe fisierele tintite, iar revizia SVN este raportata.
3. Dupa commit: `roa_sync.bat` a rulat si raporteaza succes (exit 0, commit `sync SVN rN` pe
`main` in COMUN, fara avertismente de conflict).
4. `git status` curat pe fisierele tintite in COMUN; branch-urile `claude/*` terminate sunt
inchise, cele active raportate.
5. Verificarea se face inainte de `svn commit`: fara "da" explicit, commit-ul nu are loc
(skill-ul nu comite din proprie initiativa).
6. Daca utilizatorul a zis "nu": dovada e absenta oricarei urme de commit sau sync in log.
## Scripturi
- `D:\ROA\ROAFACTURARE\roa_sync.bat` (sau `roa_sync.bat` din radacina proiectului).
- `D:\ROA\ROAFACTURARE\COMUN\scripts\roa_sync.ps1` - `-ProjectRoot <root>`, iar `-GitOnly`
sare peste SVN si regenerarea textelor.
- `D:\ROA\UTIL\foxbin2prg\txt2vcx.ps1` si `git_sync.ps1` - doar daca ai atins binare.
## Referinta (doar la nevoie)
- Doar daca ai nevoie de contextul complet, citeste `COMUN\docs\fluxul_svn_git.md`
(cazuri, interdictii) pentru fluxul SVN + git.

View File

@@ -0,0 +1,92 @@
---
name: roa-oracle-diagnostic
description: Diagnostic read-only pentru doua incidente de productie la clientii ROA - actualizarea automata (PACK_UPDATE) care nu se termina si tablespace-ul/discul Oracle plin. Foloseste-l OBLIGATORIU cand utilizatorul spune "actualizarea nu se termina la clientul X", "a ramas blocat la pasul 3", "update-ul nu a mers azi", "verifica de ce n-a mers update-ul", "clientul are baza plina", "spatiu insuficient la client", "ORA-01653", "ORA-01654", "nu mai pot salva in baza", "tablespace plin" sau cere sa verifici de ce a esuat o actualizare. Ruleaza intai scriptul de diagnostic dedicat, citeste dovada exacta (starea rularii, scriptul candidat, consumatorul de spatiu, procentul tablespace-ului) si abia apoi da verdictul. Nu improviza interogari manuale, nu relansa update-ul si nu rula curatare pana nu stii pasul exact unde s-a oprit.
---
# Diagnostic ROA: actualizare blocata si spatiu Oracle plin
Doua incidente, acelasi tipar: simptomul din aplicatie nu spune cauza. Fie actualizarea nu se
termina (`stare=3` fara `dataora_end`, log mut), fie baza/discul e plin (`ORA-01653`/`ORA-01654`,
blocaj brusc). Diagnostic STRICT read-only: se identifica pasul/consumatorul exact si se propune deblocarea.
## Cand se foloseste
- "actualizarea nu se termina la clientul X", "a ramas la pasul 3", "update-ul nu a mers azi";
- "verifica de ce n-a mers actualizarea", "de ce nu s-a actualizat baza la client";
- "clientul are baza plina", "spatiu insuficient", "ORA-01653", "ORA-01654", "ORA-00257";
- orice cerere de a verifica o actualizare esuata sau un tablespace/disk plin la un client; nu
improviza interogari si nu relansa update-ul inainte de verdictul scriptului.
## Pasi
Ambele: intai conexiunea (tunel SSH pe 1521 + alias TNS pe `127.0.0.1` in
`D:\ROA\instantclient_19_18\tnsnames.ora`, user `contafin_oracle`), apoi scriptul dedicat.
A. Actualizarea nu se termina (job `UPDATEROA_ZILNIC` -> `PACK_UPDATE.UpdateROA`):
1. Ruleaza `diag_actualizare.ps1 -Alias <ALIAS>` si citeste verdictul: pana unde a ajuns si unde e
eroarea. Lantul e secvential, o etapa picata le opreste pe toate: UpdateApp -> UpdateScripts ->
UpdateDatabaseSqlPlus.
2. `UPD_ISTORIC`: `stare` (0=start, 1=aplicatii, 2=scripturi, 3=aplicare, 4=incheiat) si
`dataora_end` gol = neincheiata; un sir oprit la aceeasi `stare` arata etapa vinovata.
3. `UPD_LOG`: `dataora_start` = rularea, `secventa`+`dataora` = ordinea, `explicatie` (CLOB) =
cauza reala. Se goleste la fiecare rulare - iei logul curent sau relansezi ca sa-l reproduce.
4. Versiuni: `UPD_DATABASE.script_date` vs `<schema>.VERSIUNE.data_script`. La `stare=3` fara
`dataora_end` cu `VERSIUNE` blocat, candidatul e primul script `UPD_DATABASE` neaplicat.
5. `script_master.log` in `DMPDIR` (`C:\DMPDIR`) pe server = mesajul real al aplicarii. Eroarea
de aici NU ajunge in `UPD_LOG`; se citeste si prin `UTL_FILE` fara RDP (util e la sfarsit).
6. Descarcare: daca etapa 1 a picat, verifica intai folderul Oracle `UPD_<PROGRAM>` pe disc
(`dbms_lob.fileexists(bfilename(...))`) si `all_directories like 'UPD%'`, nu reteaua.
7. `GetStare` refuza o rulare noua cat timp precedenta e neincheiata si n-a trecut o ora.
Deblocarea e doar sugestie - nu o executa fara acordul explicit.
B. Spatiu Oracle plin:
1. Ruleaza `diag_spatiu.ps1 -Alias <ALIAS> -Disc` (cu `-Disc` doar pe servere Windows) si citeste
verdictul plus topul segmentelor.
2. Tablespace + datafile: ce e plin, `maxbytes` vs `bytes`, cat mai poate creste. Un `maxbytes`
sub XE 11/12 GB nu e limita XE - e plafon pus la instalare.
3. Top consumatori: tablespace cu `maxbytes` atins, `SYSAUX` (SQL Tuning Sets/AWR/audit), fisiere
`.aud` din `adump` (NU se vad din SQL, doar cu `-Disc`), alert log/trace/ADR, FRA plina cu
`ARCHIVELOG` (`ORA-00257`/`ORA-19809`), recyclebin/UNDO/TEMP, logurile aplicatiei care cresc.
4. Daca eroarea de actualizare nu priveste obiectele atinse de script, suspecteaza intai spatiul
(ex. `ORA-01653 table SYS.INFO in tablespace SYSTEM`, ridicata din `SYS.PINFO`).
Cand A si B se intersecteaza, verdictul leaga scriptul candidat de tablespace-ul plin.
## Capcane
- `UPD_LOG` se goleste la fiecare rulare - nu ai istoric; altfel relansezi orbeste.
- Eroarea din `additional_info`-ul jobului e de obicei falsa: `EmailLog` ridica `ORA-20000` (SMTP
picat) si mascheaza eroarea originala. Cauza adevarata e in `UPD_LOG`.
- UpdateApp abandoneaza TOT la primul program nedescarcat (`CT_INSUCCES`): un fisier indisponibil
blocheaza la infinit, tacut (`Fisierul NU s-a salvat pe disc.`). Verifica folderul, nu reteaua.
- Ordinea de citit cand actualizarea e muta: `UPD_ISTORIC` -> `UPD_LOG` -> `script_master.log`;
daca sari direct la logul de SQL, nu stii pe ce schema si pe ce rulare te uiti.
- Erorile de script fac `UPD_LOG` curat (`WHENEVER SQLERROR EXIT ... ROLLBACK`); semnul e
`stare=3` fara `dataora_end` + `VERSIUNE` blocat. Mesajul real e doar in `script_master.log`.
- `adrci purge` si `alter database datafile ... autoextend` sunt DOAR sugestii afisate; nu le rula
automat si niciodata fara acordul explicit al utilizatorului.
- La clienti Oracle 10g lipsesc coloane din `dba_scheduler_*` - foloseste `dba_scheduler_job_run_details`.
- `-Disc` scrie un `.ps1` temporar in `DMPDIR`; pe Linux expira dupa 60s cu mesaj.
## Criterii de acceptare si dovada
Fara dovezile de mai jos nu exista verdict; diagnosticul NU se raporteaza ca terminat.
1. Verdictul scriptului e citat. Ramura A: `stare` + `dataora_end` din `UPD_ISTORIC` si linia
exacta din `UPD_LOG`/`script_master.log`. Ramura B: procentul tablespace-ului vinovat + `maxbytes`.
2. Scriptul candidat e numit exact (primul din `UPD_DATABASE` de dupa ultima versiune), nu
"undeva in etapa 3".
3. Dovada e legata de obiectele reale: daca mesajul Oracle nu priveste ce atinge scriptul, s-a
dovedit spatiul (procent + plafon), nu scriptul.
4. Deblocarea propusa e scrisa ca sugestie cu comanda exacta, neexecutata.
## Scripturi
- `D:\ROA\ROAFACTURARE\COMUN\utile\diag_actualizare.ps1`
`powershell -File D:\ROA\ROAFACTURARE\COMUN\utile\diag_actualizare.ps1 -Alias <ALIAS> [-DoarLog] [-Disc]`
- `D:\ROA\ROAFACTURARE\COMUN\utile\diag_spatiu.ps1`
`powershell -File D:\ROA\ROAFACTURARE\COMUN\utile\diag_spatiu.ps1 -Alias <ALIAS> [-Disc] [-SysPassword <parola>] [-Top 30]`
## Referinta (doar la nevoie)
- Doar daca ramura A (actualizare) sau ramura B (spatiu) cere detalii, citeste `COMUN\docs\depanare-pack-update.md` si `COMUN\docs\depanare-spatiu-oracle.md`.

View File

@@ -0,0 +1,121 @@
---
name: roa-oracle-migration
description: Scrie si publica scripturi de migrare Oracle pentru suita ROA: script nou de schema (tabela, view, procedura, functie, pachet), coloana noua, numerotare YYYY_MM_DD_NN, incheiere cu UpdateVersiune + commit, import in UPD_DATABASE si arhiva lunara catre clienti, plus re-exportul sursei unui pachet PL/SQL. Foloseste skill-ul cand auzi "scrie un script de migrare pentru...", "publica scripturile catre clienti", "am nevoie de o coloana noua pe...", "exporta sursa pachetului Y", "modific un pachet", "adaug o optiune de meniu", "rulez scripturile la client". Acopera exact capcanele care nu dau nicio eroare: o instructiune fara `;` e aruncata tacut de SQL*Plus, un NN refolosit colizioneaza intre prefixe, iar o constructie 11g+ pica doar la clientul pe Oracle 10.2. Cere compatibilitate 10.2, idempotenta si sursa de referinta MARIUSM_AUTO.
---
# roa-oracle-migration
Scrie scripturi de migrare Oracle si publica-le catre clienti. Detalii de context: vezi Referinta, doar cand ramura o cere.
## Cand se foloseste
- schimbi schema: tabela, view, procedura, functie sau pachet;
- iti trebuie o coloana noua / index / constrangere pe un obiect existent;
- scrii un script nou in `D:\ROA\DATABASE\SCRIPTURI_CLAR`;
- adaugi o optiune de meniu sau un drept (`co_..._OBIECTE.sql`);
- publici scripturile catre clienti (import in `UPD_DATABASE` + arhiva lunara);
- re-exporti sursa unui pachet PL/SQL sau citesti un corp de pachet (`oracle_export.md`).
Un agent **scrie** scriptul, nu il aplica. Rularea pe o schema se face doar la cererea explicita
a lui Marius.
## Pasi
1. Confirma ca sursa de referinta `MARIUSM_AUTO` (pe `ROA_CENTRAL`) e la zi: compara scripturile
din `SCRIPTURI_CLAR` cu inregistrarile din `VERSIUNE`. Daca lipseste vreunul, opreste-te si
semnaleaza-i lui Marius. Nu porni de la sursa citita dintr-o schema de client.
2. Alege numele `<prefix>_YYYY_MM_DD_NN_<subiect>.sql`. Prefixe in uz: `ff`, `co`, `sys`, `ris`,
`rf`. `NN` e o secventa **unica pe zi, comuna tuturor prefixelor**.
3. Scrie scriptul ASCII, CRLF, **scoped** pe cazul tratat si **idempotent** (`merge`,
`where <coloana> is null`). Un pachet sta singur in scriptul lui, fara alt DDL/DML alaturi.
4. Termina **fiecare instructiune** explicit cu `;` (sau `/` pentru blocurile PL/SQL).
5. Scrie la nivel **Oracle 10.2** - numitorul comun al parcului de clienti. Proba se face pe
serverul cel mai vechi (ROMCONSTRUCT), nu pe dev.
6. Incheie scriptul cu:
```sql
exec pack_migrare.UpdateVersiune('<nume_script>');
commit;
```
Numele se da **fara** `.sql` (extensia o adauga `UpdateVersiune`). `UpdateVersiune` e DML:
fara `commit` explicit, versiunea nu se inregistreaza si `VERSIUNE` minte.
7. Publica: intai `-DryRun`, apoi fara, conform `publicare-scripturi-db.md`:
```powershell
powershell -File D:\ROA\ROAFACTURARE\COMUN\utile\publicare_scripturi.ps1 -DryRun
powershell -File D:\ROA\ROAFACTURARE\COMUN\utile\publicare_scripturi.ps1
```
Publicarea = import al fiecarui `.sql` in `CONTAFIN_ORACLE.UPD_DATABASE` (pe `ROA_CENTRAL`)
**si** regenarea arhivei lunare in `Y:\ROAUPDATE\_UPDATE\`. Directorul e live.
8. Dupa publicare, strange dovezile din "Criterii de acceptare si dovada".
## Capcane
- **O instructiune fara `;` final e ARUNCATA fara nicio eroare.** Prima linie goala inchide
bufferul SQL*Plus; scriptul continua pana la `UpdateVersiune`, se marcheaza aplicat, iar
obiectul ramane cel vechi. `WHENEVER SQLERROR EXIT` nu prinde cazul.
- **`;` la capatul unei linii dintr-un comentariu `--` din interiorul unei instructiuni**
termina instructiunea acolo: restul `CREATE VIEW`-ului se interpreteaza linie cu linie
(`SP2-0734`, `ORA-00936` derutant). Pune `.` in loc de `;` in comentariu. Comentariile de
dinaintea instructiunii nu sunt afectate.
- **`NN` se colizioneaza intre prefixe**: acelasi numar nu se reia intr-un alt prefix in aceeasi
zi. Un script deja in `VERSIUNE` nu se reaplica; corectia se livreaza ca script nou, cu data
si `NN` noi - nu prin republicarea aceluiasi fisier.
- **Constructiile 11g+/12c+ trec pe dev dar pica la clientul 10.2** (`REGEXP_COUNT`, `CONTINUE`,
`LISTAGG`, `PIVOT`, `FETCH FIRST`, identificatori peste 30 de caractere...). Detalii in
`scripturi-migrare-db.md`.
- **Un obiect nu se poate referi la ceva creat de un script ulterior.** Daca un script creeaza
tabela si altul pachetul care o foloseste, tabela are `NN` mai mic.
- **LF in loc de CRLF** sparge parsarea pe client (`ALINES` pe `CHR(13)+CHR(10)`): tot fisierul
devine un rand. Publicarea refuza fisierele LF.
- **DDL neprefixat merge pe schema conexiunii.** Rularea manuala se face conectat pe schema
tinta (`CONTAFIN_ORACLE` pentru `co_`, schema firmei pentru `ff_`), niciodata pe un user de
lucru. `PACK_UPDATE` face singur `CONNECT` pe schema corecta.
- **`linesize` prea mic corupe tacut exportul**: SQL*Plus rupe liniile lungi, inclusiv prin
mijlocul unui identificator. Foloseste `linesize 32767`; exportul e pentru citit, nu baza
pentru un script de migrare.
- Niciodata `select` de raportare in script, `insert` fara `ID_OBIECT` explicit, sau `ID_GRUP`
fix (grupurile difera per client).
## Criterii de acceptare si dovada
Fara dovada nu se spune "gata". Ruleaza si pastreaza iesirea:
- fisierul e **ASCII pur** (0 octeti > 127), fara BOM, cu **CRLF**;
- ultima linie a fiecarui `CREATE`/`ALTER`/`MERGE` se termina cu `;`;
- scriptul se termina cu `UpdateVersiune('<nume_fara_.sql>')` urmat de `commit;`;
- `NN` e liber: nu exista rand in `VERSIUNE` si nici fisier cu acelasi `data||NN`;
- dupa aplicare, `select script_final from versiune where script_final = '<nume>.sql';` intoarce
randul si nu `..._.sql.sql`;
- `publicare_scripturi.ps1 -DryRun` listeaza scripturile fara sa scrie; arhiva generata se
citeste inapoi si se compara lungime + MD5 per fisier;
- importul compara CLOB-ul citit inapoi octet cu octet cu fisierul.
Verificarea de ASCII + CRLF (trebuie sa intoarca `0` si `True`):
```powershell
$b = [IO.File]::ReadAllBytes('D:\ROA\DATABASE\SCRIPTURI_CLAR\<script>.sql')
($b | Where-Object { $_ -gt 127 }).Count
($b -join ',').Contains('13,10')
```
## Scripturi
- `D:\ROA\ROAFACTURARE\COMUN\utile\publicare_scripturi.ps1` - publicare (import + arhiva).
Optiuni: `-DryRun`, `-Luna YYYY-MM`, `-Fisier a.sql,b.sql`, `-DoarImport`, `-DoarArhiva`,
`-Toate`. Nu copia scriptul - vezi `publicare-scripturi-db.md`.
- Incheierea obligatorie din fiecare script de migrare:
```sql
exec pack_migrare.UpdateVersiune('<nume_script_fara_.sql>');
commit;
```
- Export sursa pachet: `linesize 32767`, apoi verifica sa nu existe linii la exact `linesize`
(`oracle_export.md`).
## Referinta (doar la nevoie)
- Doar daca scrii un script nou sau ai dubii despre scheme si conectare, citeste `COMUN\docs\scripturi-migrare-db.md` si `COMUN\docs\conventie_mediu_oracle.md`.

View File

@@ -0,0 +1,91 @@
---
name: roa-oracle-production-access
description: Ridica lantul complet statie -> tunel SSH Bitvise -> alias TNS -> DSN ODBC 32-bit si interogheaza read-only un Oracle de PRODUCTIE al unui client ROA. Foloseste-l OBLIGATORIU cand utilizatorul spune "conecteaza-te la productia clientului X", "intra pe Oracle la client", "ridica tunelul SSH", "deschide tunelul Bitvise", "nu merge DSN-ul ODBC", "ORA-12541 TNS:no listener", "verifica TNS", "unde e tnsnames.ora", "seteaza TNS_ADMIN", "ruleaza un SELECT pe productie", "de ce nu se conecteaza sqlplus" sau cere orice acces la o baza Oracle care nu e expusa direct. Stapaneste exact capcanele care costa sesiuni intregi: portul local 1521 unic pentru mai multe destinatii, `BvSsh.exe` care tine portul legat, `odbcad32.exe` pe 32 de biti, `$` distrus in here-string PowerShell. Nu improviza, nu sari peste dovada si nu ghici parole.
---
# Acces la productia Oracle: tunel SSH (Bitvise) + TNS + DSN ODBC
Scopul e sa NU se mai piarda o sesiune redescoperind lantul: executa pasii de mai jos, nu trimite utilizatorul in alta parte.
## Cand se foloseste
- "conecteaza-te la productia clientului X", "intra pe Oracle la client", "deschide baza de productie";
- "ridica tunelul SSH", "porneste tunelul Bitvise", "profilul vending.tlp / conpress.tlp";
- "nu merge DSN-ul ODBC", "eroare `ORA-12541: TNS:no listener`", "sqlplus nu se conecteaza";
- "verifica TNS", "unde e tnsnames.ora", "seteaza `TNS_ADMIN`", "SELECT pe productie" (read-only).
Nu acopera: re-exportul sursei unui pachet PL/SQL si editarea de text VFP.
## Pasi
1. Fixeaza lantul. TREI nume trebuie sa se potriveasca, si e usor de ratat:
- **profilul Bitvise** (`.tlp`) - ce port local deschide (de obicei `127.0.0.1:1521`);
- **aliasul TNS** din `tnsnames.ora` - ce host:port foloseste (`127.0.0.1:1521`);
- **DSN-ul ODBC** - ce alias TNS foloseste. `tcHost` din `oConn.Connect(tcHost, ...)`
(`oproceduri_comune.prg`) e NUMELE DSN-ului, nu un host si nu un alias TNS.
2. Profilele Bitvise sunt pe statia curenta in `D:\roa\BITVISE\` (`vending.tlp`, `conpress.tlp`,
`clever.tlp`, `automotive.tlp`, `avis.tlp`, `eduard.tlp`, `ems.tlp`, `romfast.tlp`, `sigma.tlp`, `vadeco.tlp`).
3. Ridica tunelul fara interfata grafica:
`& 'C:\Program Files (x86)\Bitvise SSH Client\stnlc.exe' -profile=D:\roa\BITVISE\vending.tlp`
(sau `Start-Process ... -WindowStyle Hidden` cu redirect in `$env:TEMP\stnlc_out.txt`).
`stnlc` foloseste singur parola din profil: NU o pune pe linia de comanda si ruleaza sub
acelasi cont Windows sub care a fost salvat profilul, altfel DPAPI nu poate decripta.
4. Verifica tunelul inainte de orice altceva (nu te baza ca procesul traieste):
`(Test-NetConnection 127.0.0.1 -Port 1521 -WarningAction SilentlyContinue).TcpTestSucceeded`.
5. Seteaza TNS: `$env:TNS_ADMIN = 'D:\ROA\instantclient_11_2_0_2'` pentru driverul ODBC pe 32 de
biti, sau `instantclient_19_18` pentru `sqlplus`. Aliasul care merge prin tunel are
`HOST = 127.0.0.1`, `PORT = 1521`.
6. DSN: sunt pe 32 de biti, in `HKLM\SOFTWARE\WOW6432Node\ODBC\ODBC.INI`. Listeaza cu
`Get-ChildItem 'HKLM:\SOFTWARE\WOW6432Node\ODBC\ODBC.INI' | Select-Object -ExpandProperty PSChildName`.
Administrare vizuala: `C:\Windows\SysWOW64\odbcad32.exe` (**NU** cel din `System32`).
7. Interogheaza: `& 'D:\ROA\instantclient_19_18\sqlplus.exe' -S -L '<schema>/<parola>@VENDING' '@script.sql'`.
`-L` = o singura incercare de login. **Foloseste-l intotdeauna pe productie.**
8. Prima interogare, mereu, ca sa stii unde ai nimerit:
`select user || ' @ ' || sys_context('USERENV','DB_NAME') from dual;`
9. La final inchide tunelul (opreste procesul `stnlc`); nu-l lasa deschis peste noapte.
## Capcane
- **Port local 1521 unic pentru mai multe destinatii**: daca ai deja un tunel catre alt client,
al doilea nu mai poate lega portul si interoghezi **alta baza** fara sa-ti dai seama. Proba e
pasul 8.
- **`BvSsh.exe` (GUI)** poate tine portul 1521 legat pe `0.0.0.0` cu o sesiune moarta -> `sqlplus`
da `ORA-12541: TNS:no listener`, desi "tunelul pare sus". `stnlc` isi poate lega totusi regula
pe `127.0.0.1:1521` peste ea. Nu te lua dupa `netstat`: proba reala e interogarea.
- **DSN-urile sunt pe 32 de biti** (VFP e pe 32 de biti) si stau in `WOW6432Node\ODBC\ODBC.INI`;
`odbcad32.exe` din `System32` e pe 64 de biti si nu le vede.
- **`$` in here-string** cu ghilimele duble (`@"..."@`): PowerShell expandeaza `$`, iar un regex
Oracle care se termina cu ancora `$` ajunge stricat in `.sql` -> interogarea intoarce 0 randuri
**fara nicio eroare**. Foloseste here-string cu apostrof (`@'...'@`).
- **`termout off`** ascunde si mesajele de eroare: un spool gol nu inseamna date lipsa; ruleaza din
nou fara `termout off` inainte sa banuiesti datele.
- **Fara credentiale in git** si **fara ghicit parole**: `FAILED_LOGIN_ATTEMPTS` poate bloca
schema in PRODUCTIE dupa cateva incercari gresite. Cere parola.
- **Doar `SELECT`**: fara `INSERT`/`UPDATE`/`DELETE`/DDL, fara `ALTER SESSION` persistent;
margineaza cu `fetch first N rows only`. Codurile fiscale publice pot sta in repo, denumirile de
parteneri, adresele si soldurile NU.
## Criterii de acceptare si dovada
Fara dovezile da/nu de mai jos, conexiunea NU se raporteaza ca functionala. "S-a conectat" nu e dovada.
1. `(Test-NetConnection 127.0.0.1 -Port 1521 -WarningAction SilentlyContinue).TcpTestSucceeded` -> `True`.
2. In `%TEMP%\stnlc_out.txt` apare `Added client-to-server forwarding rule on 127.0.0.1:1521 to localhost:1521.`
3. `sqlplus -S -L` chiar intoarce randuri din
`select user || ' @ ' || sys_context('USERENV','DB_NAME') from dual;` -> schema si baza asteptate.
4. DSN-ul rezolva la host:port corect: numele e prezent in `WOW6432Node\ODBC\ODBC.INI`, iar
aliasul TNS folosit de el arata spre `127.0.0.1:1521`.
5. Tunelul e inchis la final (procesul `stnlc` oprit).
## Scripturi
- `C:\Program Files (x86)\Bitvise SSH Client\stnlc.exe` - tunel din linie de comanda.
- `C:\Program Files (x86)\Bitvise SSH Client\BvSsh.exe` - GUI (sursa capcanei de port).
- `D:\ROA\instantclient_19_18\sqlplus.exe` - interogare read-only.
- `D:\ROA\instantclient_11_2_0_2\` - clientul pentru driverul ODBC pe 32 de biti (`TNS_ADMIN`).
- `C:\Windows\SysWOW64\odbcad32.exe` - administrarea DSN-urilor pe 32 de biti.
- `Test-NetConnection` (PowerShell) - proba ca tunelul e sus.
## Referinta (doar la nevoie)
- Doar daca ai nevoie de tabelul schemelor cunoscute (profil -> alias/DSN -> schema) sau de exemplul de extragere a codurilor fiscale, citeste `COMUN\docs\conexiuni-tunel-ssh-odbc.md`; re-exportul sursei unui pachet PL/SQL nu e acoperit aici, vezi `COMUN\docs\oracle_export.md`.

View File

@@ -0,0 +1,70 @@
---
name: roa-oracle-sqlexec-no-data
description: Prinde si elimina pierderea tacuta de randuri intre VFP si Oracle - o procedura PL/SQL cu SELECT ... INTO care nu gaseste niciun rand arunca NO_DATA_FOUND (ORA-01403), iar prin SQLEXEC driver-ul il raporteaza ca SUCCES: SQLExec() intoarce 1, bucla din VFP nu vede nicio eroare si randurile lipsesc fara urma in log. Foloseste-l OBLIGATORIU cand auzi "SQLEXEC nu intoarce nimic", "lipsesc randuri", "procedura intoarce gol dar fara eroare", "documentul se salveaza cu mai putine linii", "totalurile ies mai mici dar consistente", "SELECT INTO", "NO_DATA_FOUND", "ORA-01403", sau cand scrii ori modifici o procedura apelata din VFP si trebuie sa stii de ce intoarce mai putine randuri decat ar trebui. Nu accepta "a mers" fara numaratoare de randuri.
---
# Randuri pierdute tacit intre VFP si Oracle (SQLEXEC + NO_DATA_FOUND)
O eroare reala in Oracle devine succes raportat in VFP. Mecanismul care ascunde defectul
ramane activ pana cand e inchis explicit, iar orice reparatie ulterioara ramane neverificabila.
## Cand se foloseste
- Un apel VFP catre o procedura PL/SQL intoarce mai putine randuri sau zero, fara eroare.
- Triggeri textuali: "SQLEXEC nu intoarce nimic", "lipsesc randuri", "procedura intoarce gol
dar fara eroare", "documentul are mai putine linii decat pe ecran", "SELECT INTO".
- Scrii sau modifici o procedura care contine `SELECT ... INTO` si e apelata din VFP.
- `SQLExec()` intoarce 1 (succes), dar cursorul rezultat e gol sau incomplet.
## Regula
1. **Garda intai, cauza dupa.** Infasoara FIECARE `SELECT ... INTO` in
`BEGIN ... EXCEPTION WHEN NO_DATA_FOUND THEN RAISE_APPLICATION_ERROR(-20000, '...')`.
Codul de eroare (`FACT-0xx`) se cauta liber in `USER_SOURCE` inainte de a-l folosi.
2. Daca absenta randului e legitima, nu o lasa sa cada: foloseste un agregat
(`SELECT COUNT(*)/MAX(...) INTO`) sau un cursor care intoarce garantat un rand.
3. Simetrizeaza predicatul pe coloana SI pe parametru - sirul gol din VFP este NULL in Oracle:
-- gresit: 'XXXX' = NULL da UNKNOWN, deci zero randuri
AND NVL(A.CONT, 'XXXX') = V_CONT
-- corect:
AND NVL(A.CONT, 'XXXX') = NVL(V_CONT, 'XXXX')
4. **Partea VFP e obligatorie.** `SQLExec()` nu e sursa de adevar. Testeaza numarul de randuri:
lnRez = SQLEXEC(lnH, "begin PACK_FACTURARE.adauga_articol_factura(...); end;", "curRez")
IF lnRez < 0 OR RECCOUNT("curRez") = 0
* trateaza ca esec, nu ca succes
ENDIF
Nu raporta "functioneaza" pe baza lui `lnSucces`.
## Capcane
- **`SQLExec()` intoarce 1 la `SQL_NO_DATA`** - `If lnSucces < 0` nu prinde nimic, linia dispare
fara urma. Verificarea de succes trebuie sa includa si numarul de randuri.
- **Sirul gol este NULL in Oracle**: `'XXXX' = NULL` da UNKNOWN, deci predicatul cade pe zero
randuri. Santinela doar pe coloana nu e suficienta.
- **Camp lipsa din lista de insert** a cursorului, pe o coloana numerica fara `NULL`: ramane `0`
tacit, nu NULL si nu eroare, iar predicatul cade pe `0` - acelasi simptom, alta cauza.
- **Fara asertiune pe numarul de linii**, toate testele existente trec: identitatea documentului
si legaturile lui se pastreaza corect, doar liniile lipsesc.
- Nu presupune ca un cod `FACT-0xx` e liber - verifica-l in `USER_SOURCE`.
## Criterii de acceptare si dovada
Fara dovada da/nu de mai jos, defectul NU se raporteaza ca reparat.
1. Numaratoarea de randuri e asertata intr-un test headless pe fluxul real: acelasi document,
acelasi input, `RECCOUNT()` / `SELECT COUNT(*)` egal cu numarul de linii din sursa. Un total
mai mic nu e acceptabil, chiar daca nu exista eroare.
2. Handler-ul exista pe disc, nu doar in intentie - interogare pe `USER_SOURCE` care arata
`EXCEPTION WHEN NO_DATA_FOUND` in ramura procedurii vizate.
3. Test negativ: forteaza un predicat care nu potriveste nimic si confirma ca primesti eroare
`-20000` in VFP, nu succes cu cursor gol.
4. "Arata bine" / "nu a dat eroare" nu e dovada. Dovada e numarul de randuri.
## Referinta (doar la nevoie)
- `COMUN\docs\capcana_sqlexec_no_data_found.md` - mecanismul complet, precedentul
`PACK_FACTURARE.adauga_articol_factura` si modul de cautare preventiva in `USER_SOURCE`.

View File

@@ -0,0 +1,66 @@
---
name: roa-vfp-go-recno
description: Prinde capcana `GO <recno>` in Visual FoxPro 9 cand numarul de inregistrare a fost capturat mai devreme sau primit ca parametru - cursorul s-a schimbat intre timp, iar `GO` fie da "Record is out of range", fie aseaza pointerul pe alt rand, tacit, fara eroare. Foloseste-l OBLIGATORIU cand vezi `GO` pe o variabila de recno (`lnRecno = Recno()`, `GO tnRecNo`, `Recno(alias)` refolosit), cand exista un `SELECT-SQL` sau requery intre captura si pozitionare, sau cand auzi "s-a pozitionat pe alt rand", "dupa requery sare altundeva", "recno capturat", "GO pe Recno()", "nu s-a dus pe inregistrarea ceruta", "record is out of range", "GO-ul nu nimereste". Nu repara cu `IF BETWEEN(...)` si nu te multumi cu "nu a dat eroare" - inlocuieste cu repozitionare pe cheie unica (`LOCATE`/`SEEK`) sau impacheteaza `GO` in `TRY/CATCH`.
---
# GO pe Recno() capturat - repozitionare sigura
`GO <n>` e pozitionare absoluta pe numarul curent de ordine al cursorului. Daca `n` a fost capturat
inainte de un `SELECT-SQL`, `APPEND`, `DELETE`, `PACK` sau orice requery, `n` nu mai desemneaza acelasi
rand. De multe ori nu primesti eroare, doar alt rand.
## Cand se foloseste
Opreste-te si aplica regula cand:
- vezi `GO <variabila>` sau `GO (expresie)` unde expresia e un recno pastrat (`lnRecno = Recno()`, `Recno(alias)`, `RECNO()`);
- o rutina primeste un recno ca parametru (`LPARAMETERS tnRecNo`) si face `GO tnRecNo`;
- intre capturarea recno-ului si `GO` exista un `SELECT ... FROM` sau orice requery pe acelasi cursor;
- auzi "s-a pozitionat pe alt rand", "dupa requery sare altundeva", "recno capturat", "nu s-a dus pe inregistrarea ceruta", "record is out of range".
`GO TOP` / `GO BOTTOM` nu poarta recno, dar se re-evalueaza si ele dupa requery.
## Regula
Nu folosi `GO` pe un recno capturat anterior sau primit ca parametru. Repozitioneaza prin cheia unica
de rand (ex. `doc_key`), care nu depinde de ordinea cursorului.
WRONG - recno capturat, apoi requery, apoi `GO`:
```foxpro
lnRecno = Recno("facturi")
SELECT SUM(valoare) FROM facturi WHERE ... INTO CURSOR tmp
GO lnRecno && sursa a ramas pe EOF -> "Record is out of range" sau alt rand
```
RIGHT - cheie unica, refacuta dupa requery:
```foxpro
lcKey = facturi.doc_key
SELECT SUM(valoare) FROM facturi WHERE ... INTO CURSOR tmp
SELECT facturi
LOCATE FOR doc_key = lcKey && sau SEEK( lcKey, "facturi", "tag_doc_key" )
IF NOT FOUND()
GO TOP && randul a disparut intre timp - decizie explicita
ENDIF
```
Daca nu exista cheie si ramai la recno (rar), impacheteaza-l: `TRY` / `GO tnRecNo` / `CATCH` / `GO TOP` / `ENDTRY`.
## Capcane
- `SELECT-SQL ... FROM cursor` lasa pointerul sursei pe EOF (`Reccount()+1`); `GO` pe un recno capturat inainte de `SELECT` da "Record is out of range" - pointerul nu revine singur.
- `Recno()` fara alias se citeste pe aliasul selectat; un `SELECT` intre timp intoarce alt numar. Captureaza cu alias explicit, inainte de requery.
- Randurile pot disparea intre timp (DELETE + PACK, filtru schimbat, `SET ORDER`); un `LOCATE` care intoarce `.F.` e informatie reala - trateaz-o explicit.
- `GO` in afara `[1, Reccount()]` da eroare; in interior NU da niciodata. "A mers" nu inseamna "s-a dus corect"; fara assert pe cheie nu ai dovada.
- Nu repara cu `IF BETWEEN(tnRecNo, 1, Reccount())` - trece, dar poate cadea pe alt rand. Doar re-`LOCATE` pe cheie rezolva.
- `SEEK` cere tag-ul/ordinea potrivita; dupa un `SET ORDER` gresit rateaza tacit. Verifica `ORDER()` sau foloseste `LOCATE`.
## Criterii de acceptare si dovada
Fara dovada da/nu nu raporta ca rezolvat; "nu a dat eroare" nu e dovada.
1. Dupa fiecare requery, assert pe cheia randului curent (`ASSERT doc_key == lcKey`, sau log daca difera). Recno-ul nu e dovada, cheia e.
2. Test headless pe scenariul real: captureaza cheia, forteaza un requery pe acelasi cursor, repozitioneaza, apoi verifica `doc_key` si campurile randului asteptat.
3. Caz limita: randul capturat dispare intre timp -> rutina cade pe `GO TOP` sau pe calea decisa, fara eroare, cu log.
4. Regresii: zero `GO <variabila_recno>` ramase in modul (`grep -rn "GO " <module>`).
## Referinta (doar la nevoie)
- `COMUN\docs\conventie_go_recno.md` - nota de conventie (EOF dupa `SELECT-SQL`, varianta TRY/CATCH).

View File

@@ -0,0 +1,109 @@
---
name: roa-vfp-headless-test
description: Ruleaza si depaneaza teste VFP 9 fara IDE (headless) cu vfp9.exe -A -T "<script.prg>" <param> din PowerShell, plus testele UI cu formular real, harness si screenshots. Foloseste skill-ul cand utilizatorul cere sa "ruleaza testul headless", sa "reproduca bug-ul", sa "scrie un test UI pentru formularul X", sa "depaneze eroarea la runtime", sa vada "de ce s-a agatat testul", sa interpreteze log.txt sau sa verifice ca testul chiar a rulat dupa un fix. Acopera lantul complet: init mediu auto, precompilare izolata, rulare cu timeout, watchdog pentru dialoguri native, raport de rezultate. Nu re-descoperi capcanele deja platite (SAFETY ON sub -A, .FXP/.ERR stale, INKEY(0) blocant, mock-uri in fisierul testat, DO...WITH prin referinta, dialog "View Parameter") - sunt distilate aici, iar sursa canonica e in COMUN\docs.
---
# Rulare si depanare teste VFP headless (fara IDE)
Distinge "headless" (script .prg, fara UI vizibil) de testul UI cu formular real si screenshots.
Ambele se lanseaza de aici; ambele au aceleasi capcane de baza. Fara log/png, testul nu exista.
## Cand se foloseste
- "ruleaza testul headless", "ruleaza testul", "de ce s-a agatat testul".
- "reproduce bug-ul", "depaneaza eroarea la runtime", "am eroare in log.txt".
- "scrie un test UI pentru formularul X", "fa screenshots la pasul Y".
- Orice VFP care pare blocat: proces viu, CPU ~0, niciun rand nou in log.
- NU pentru editare de .vcx/.scx pe text sau cautare in binare - alte skill-uri.
## Pasi
1. Alege harness-ul de mediu dupa PROIECT: `test_init_env_auto_roafacturare.prg` aici, variantele
`_roacont`/`_roagest`/`_acnpro`/`_roadef` pentru celelalte produse. Init gresit incarca alt
working copy si valideaza cod vechi, silentios.
2. Scrie scriptul: prima linie `SET SAFETY OFF` + `SET TALK OFF`, apoi PATH/CLASSLIB/PROCEDURE ca
in aplicatie, apoi `ON ERROR` care logheaza `ERROR()`, `MESSAGE()`, `PROGRAM()`, `LINENO()` cu
STRTOFILE si continua (fara WAIT WINDOW - headless nu are consola). `QUIT` obligatoriu la final.
3. Replica doar liniile relevante din Show; pentru forme modale pune `WindowType = 0`.
4. Precompileaza izolat, in proces copil: `_precompile.ps1`. Fara asta `vfp9 -A test.prg`
deschide intermitent editorul in loc sa ruleze (log gol).
5. Ruleaza cu timeout (comanda in `## Scripturi`). Pentru dialoguri native, watchdog-ul.
6. Citeste DOAR esecurile din raportul de teste, nu logurile brute.
7. Test UI: `.prg` cu `PUBLIC gcUILog, gcSyncDir`, dummy-uri reale pe INFRASTRUCTURA (goExecutor,
goApp, poAct, cursoarele cerute de Init), `WindowType=0`, `Show()`, pe fiecare pas
`DO HarnessStep WITH <n>, '<mesaj>'`, la final `DO HarnessDone WITH 'done'`. Apoi
`vfp_ui_harness.ps1 -TestPrg <test.prg> -Steps @('pas0',...)`; ruleaza-l in background sau cu
timeout 300-600s si NU porni VFP separat (orchestratorul il lanseaza singur).
8. Inainte de orice asertiune, dovedeste ca noul cod e incarcat (`ClassLibrary` = calea asteptata,
`CREATEOBJECT` pe clasa noua intr-un TRY) - altfel validezi alt working copy.
## Capcane
Condensat; fiecare are istoric scump in `COMUN\docs\depanare_testare_vfp.md` (sec. 6-7) si
`COMUN\docs\testare-ui-vfp.md`. Consulta-le inainte sa schimbi scriptul sau sa invinuiesti codul.
- `-A` si `-T` sunt obligatorii amandoua: fara `-T` procesul sta pe splash, simptom identic cu o
eroare de compilare. `-A` ignora config.fpw si porneste SAFETY ON.
- SAFETY ON sub `-A`: primul `STRTOFILE`/`COMPILE` peste fisier existent scoate dialogul modal
"overwrite?" si agata pana la timeout (proces viu, pare agatat). `SET SAFETY OFF` prima linie;
logheaza `SET("SAFETY")` si `SET("RESOURCE")` la start.
- ON ERROR: dupa RETURN executia CONTINUA => o singura cauza produce CASCADA. PRIMA eroare din
secventa e cea reala; 1940/1939 pe WITH/ENDWITH e zgomot. Erorile din TRY/CATCH nu ajung in log.
- `.FXP`/`.ERR` stale agata reluarea chiar dupa fix, sub ACELASI nume (CPU=0, Responding=True, nimic
in log; seamana cu blocaj de retea). Sterge-le la fiecare reluare sau da nume nou. Verifica daca
`.ERR` e de la rularea asta, nu de la una anterioara.
- `INKEY(0, ...)` nu e poll, e asteptare NELIMITATA: timerii VFP nu se declanseaza, nimic nu poate
debloca din interiorul procesului. Foloseste `INKEY(0.01, 'H')`.
- Mock in ACELASI fisier cu functia apelata NU e prins de `SET PROCEDURE ... ADDITIVE`: pune mock-ul
pe functia pe care o apeleaza codul TESTAT, nu pe capatul lantului; proba ca mock-ul a castigat,
INAINTE de actiunea care poate deschide dialog.
- `DO ... WITH` paseaza PRIN REFERINTA si variabila devine invizibila sub numele ei => dialog nativ
"View Parameter" blocant, fara log si fara eroare. Paranteze: `DO proc WITH (gnAn), (gnLuna)`.
- Hung vs slow: proces viu + CPU ~0 + niciun rand nou in log = asteapta un dialog sau un fisier
(exista `.ERR`?; enumera ferestrele cu `EnumWindows`/`GetWindowThreadProcessId`; `-SyncDir`
nepasat => semafoare asteptate in alt folder). CPU mare = inca ruleaza. Urmareste mtime-ul
log-ului, nu "log ne-gol" (un vfp9 zombi tine log stale).
- Timeout: rulari intermediare ~90s/test, pauza 0.5s intre pasi; retry NUMAI la "start ratat", nu la
"start lent" (la rece, zeci de classlib-uri). Fara suite in paralel - harness-ul omoara instantele
vfp9 proprii, filtrate pe linia de comanda.
- Alte capcane UI, pe scurt: fara `SELECT-SQL` pe cursorul legat de grid; `ZAP` (nu `DELETE ALL`) pe
cursorii ceruti de Init; cursorii cu EXACT indecsii fluxului real; `ALLTRIM(camp) == 'lit'`;
variabile care nu colizioneaza cu functii built-in (`loCk` -> `LOCK()`); assert pe starea de DUPA
resincronizarea formularului; `_SCREEN.ActiveForm` nu merge headless, foloseste `_SCREEN.Forms`.
- Testele exerseaza fluxul REAL al utilizatorului; popularea directa a cursoarelor da PASS fals.
- Nu injecta input real (`keybd_event`/`SendInput`) - masina e partajata; dismiss-ul se face doar cu
mesaje Windows tintite pe handle (watchdog `-AutoDismiss`).
## Criterii de acceptare si dovada
Fara dovada, testul NU s-a rulat. "Gata" nu se accepta pe baza ca procesul a terminat.
- Headless: log-ul arata numarul de PASS/FAIL si PRIMA eroare reala (nu cascada), plus
`SET("SAFETY")`/`SET("RESOURCE")` la start si `QUIT` la final. Dovada = calea log-ului + extractul
de esecuri din `raport_teste.ps1`.
- UI: PNG-uri `screenshots\step_<n>_<eticheta>.png` pentru fiecare pas si semaforul final/done.txt;
`ClassLibrary` dovedeste ca s-a incarcat codul corect.
- Un test care "trece" dar valideaza alt working copy, sau care populeaza direct cursorii in loc sa
treaca prin fluxul real, e PASS fals - echivalent cu un esec.
- Bug: dovada e log-ul cu eroarea + linia, INAINTE de fix; dupa fix, exact aceeasi comanda da PASS.
Fara ambele rulari, bug-ul nu e inchis.
## Scripturi
Toate in `D:\ROA\ROAFACTURARE\COMUN\utile\Teste\`:
- `D:\ROA\ROAFACTURARE\COMUN\utile\Teste\vfp_ui_harness.ps1` - orchestrator UI:
`powershell -File vfp_ui_harness.ps1 -TestPrg <test.prg> -Steps @('pas0','pas1')`.
- `D:\ROA\ROAFACTURARE\COMUN\utile\Teste\watchdog_vfp.ps1` - dialoguri native:
`powershell -File watchdog_vfp.ps1 -Script <test.prg> -AutoDismiss` (captura PNG + text).
- `D:\ROA\ROAFACTURARE\COMUN\utile\Teste\_precompile.ps1` - precompilare izolata, rulata in proces
copil (`Start-Process -Wait`).
- `D:\ROA\ROAFACTURARE\COMUN\utile\Teste\raport_teste.ps1` - sumar PASS/FAIL din loguri.
- Comanda headless, lansata din PowerShell cu timeout:
`vfp9.exe -A -T "<script.prg>" <param>`, cu `$p.WaitForExit(120000)` si `$p.Kill()` daca nu iese.
- Mediu: `D:\ROA\ROAFACTURARE\COMUN\utile\Teste\test_init_env_auto_roafacturare.prg`.
- Handshake UI: `ui_harness.prg`, `mock_amessagebox.prg` (incarcate cu `SET PROCEDURE ... ADDITIVE`).
## Referinta (doar la nevoie)
- Doar daca depanezi headless sau lucrezi la harness UI, citeste `COMUN\docs\depanare_testare_vfp.md` (sec. 6-7) si `COMUN\docs\testare-ui-vfp.md`.

View File

@@ -0,0 +1,62 @@
---
name: roa-vfp-null-guards
description: Scrie si verifica garzile pe valorile NULL venite din Oracle in cod VFP (campuri de cursor, comparatii, Empty(), NVL, ISNULL). Foloseste-l OBLIGATORIU cand scrii sau revezi cod VFP care compara, valideaza sau concateneaza valori din Oracle; cand apare "valoare NULL din Oracle", "Empty() da rezultat gresit", "coloana poate fi null", "SQL statement parameter is required for non-prepared SQLEXEC() calls" sau rezultate gresite/erori inselatoare pe coloane care pot fi NULL. Se declanseaza si cand NU se rosteste cuvantul "NULL": daca doar compari, validezi sau construiesti text/SQL din campuri de cursor Oracle, regula se aplica. O singura regula, dar platita scump - fara garda rezultatul e gresit sau eroarea apare departe de cauza.
---
# Garzi pe valorile NULL venite din Oracle
Un camp de cursor intors de Oracle poate fi `.NULL.`. In VFP `EMPTY(.NULL.)` intoarce `.F.`, comparatia cu NULL nu e nici adevarata nici eroare, iar concatenarea cu `+` face tot sirul `.NULL.`. O garda gresita nu da eroare: da rezultat gresit, tacit.
## Cand se foloseste
- Scrii sau revezi cod VFP care citeste, compara sau valideaza campuri de cursor Oracle.
- Folosesti `Empty()`, `Nvl()`, `IsNull()` sau `Type()` pe o valoare de cursor.
- Construiesti text, SQL, mesaje sau chei prin `+` din campuri de cursor.
- Adaugi o interogare sau o coloana noua si nu esti sigur daca poate fi NULL.
- Simptom: rezultat gresit fara eroare, sau `SQL statement parameter is required for non-prepared SQLEXEC() calls` in `OEXECUTOR.OEXECUTE`.
## Regula
Tipul se evalueaza PRIMUL, ca sa opreasca acolo, iar `Nvl(...)` se aplica INAUNTRUL lui `Empty()` sau al comparatiei - niciodata pe langa. Un singur camp NULL anuleaza tot sirul construit prin `+`, deci fiecare camp il primeste.
```foxpro
* GRESIT: Empty() singur nu prinde NULL; comparatia cu NULL e falsa; sirul devine .NULL.
If Empty(tcCont)
Return
Endif
tcSql = 'Where cont = ' + tcCont
* CORECT: Type() opreste primul, Nvl(...) sta in interiorul lui Empty() / comparatiei
If Type('tcCont') # 'C' Or Empty(Nvl(tcCont, ''))
Return
Endif
If Type('tdData') # 'D' Or Empty(Nvl(tdData, {}))
Return
Endif
If Nvl(tcStare, '') == 'A'
Return
Endif
tcSql = 'Where cont = ' + Nvl(tcCont, '')
```
## Capcane
- `EMPTY(.NULL.)` este `.F.`, deci `Empty()` singur NU prinde NULL - nici pe text, nici pe data.
- `IsNull(x) Or Empty(x)` e forma lunga si usor de uitat pe o ramura; `Empty(Nvl(x, ''))` e forma unica, greu de scris gresit.
- `TYPE()` pe o variabila caracter cu NULL intoarce tot `'C'` - nu tine loc de `Nvl`, doar prinzi tipul neasteptat cu el.
- Concatenarea prin `+` cu un camp NULL anuleaza tot sirul; eroarea apare departe, la `SQLEXEC`, nu la linia care l-a construit.
- `NVL(col, '')` in interogarea Oracle mascheaza NULL-ul in SELECT, dar nu repara codul care primeste deja cursorul - garda ramane in VFP.
- `Nvl(x, '')` pe un camp numeric/date da conversie gresita: valoarea implicita trebuie sa aiba acelasi tip (`{}`, `0`).
## Criterii de acceptare si dovada
Fara dovada da/nu de mai jos, garda NU se raporteaza ca pusa. "Arata bine" nu e dovada.
1. Dovada ca valoarea chiar poate fi NULL (nu doar goala), pe datele de test: `SELECT SUM(CASE WHEN <coloana> IS NULL THEN 1 ELSE 0 END) FROM <tabel>` -> rezultat > 0.
2. Test headless pereche: pe cursorul cu NULL, garda corectata da exact acelasi rezultat ca pe valoarea goala. Dovada = ambele cazuri trecute, cod de iesire 0.
3. Assert care cade fara fix: ruleaza testul si pe varianta fara `Nvl` - trebuie sa esueze; un test care trece in ambele variante nu dovedeste nimic.
4. Garduri ramase neacoperite in functia atinsa: `grep -n "Empty(" <fisier.prg>` -> fiecare aparitie are `Nvl(` in interior; `grep -n "' + " <fisier.prg>` -> fiecare camp de cursor are `Nvl(` inainte de `+`.
## Referinta (doar la nevoie)
- `D:\ROA\ROAFACTURARE\COMUN\docs\conventie_null_vfp.md` - contextul complet si simptomul `SQLEXEC`, daca iti trebuie dupa ce ai aplicat regula.

View File

@@ -0,0 +1,83 @@
---
name: roa-vfp-text-edit
description: Editeaza cod si proprietati din binarele VFP (.vcx/.scx) prin versiunile lor text FoxBin2Prg (.vc2/.sc2) - localizeaza metoda, editeaza byte-safe, face write-back pe binar si ruleaza verificarile obligatorii. Foloseste-l OBLIGATORIU cand utilizatorul spune "editeaza metoda X din clasa Y", "adauga o proprietate in clasa Z", "modifica procedura din formularul W", "cauta in .vcx/.scx", "unde e definit <nume>", "write-back pe binar", "txt2vcx" sau cere orice schimbare de cod care nu sta in .prg. Fluxul e cel mai predispus la coruperea ireversibila a intregului fisier: o editare gresita (diacritice rescrise, LF izolat, membru lipsa din *<DefinedPropArrayMethod>) strica tot binarul si trece tacit de fidelity-check. Nu improviza cu Edit/Write si nu sari peste verificarile de dovezi.
---
# Editare cod VFP via text (.vc2/.sc2) cu write-back pe binar
Textul `.vc2`/`.sc2` e sursa de editare; binarul `.vcx`/`.scx` (+ `.vct`/`.sct`) se regenereaza
punctual, fara VFP IDE. O singura greseala corupe ireversibil tot fisierul.
## Cand se foloseste
Orice cerere care atinge cod/proprietati din binare VFP:
- "editeaza metoda X din clasa Y", "adauga proprietatea P in clasa Z", "modifica `do_salvare`";
- "cauta in .vcx/.scx", "unde se defineste `<nume>`", "cine mosteneste clasa W";
- "write-back pe binar", "txt2vcx", "converteste .vc2 in .vcx";
- orice schimbare de cod care nu sta in `.prg` (metode de clasa / forma).
Doar `.vcx` si `.scx` au write-back. `.frx/.mnx/.lbx/.pjx/.dbc/.dbf` raman read-only - doar in IDE.
## Pasi
0. Refresh text, obligatoriu la inceput de sesiune si dupa orice sesiune IDE (staleness pe mtime):
`git_sync.ps1 -ProjectRoot <root>` (proiecte nemigrate: `vcx2txt.ps1` cu `-CacheRoot` separat).
1. Localizeaza intervalul EXACT al metodei inainte de a edita ceva:
`vfp_symbols.ps1 ... -Where '<fisier>.vc2:<linie>'` sau `-Class '<nume>'`. Fara ele nimeresti
`Init`-ul altei clase.
2. Baseline: `Copy-Item <f>.vc2 <f>.vc2.pre_runda<N>.bak` (langa binar, in arbore).
3. Cens de octeti >= 0x80 INAINTE de editare (vezi Criterii). Retine secventa exacta.
4. Editeaza byte-safe: PowerShell `[IO.File]::ReadAllText`/`WriteAllText(..., GetEncoding(1250))`.
NU folosi `Edit`/`Write` (rescriu UTF-8 si distrug diacriticele). Continut nou strict ASCII,
TAB-uri ca in jur, fara reflow.
5. Membru NOU (proprietate/metoda): adauga `*p: nume` / `*m: nume` in
`*<DefinedPropArrayMethod>`, la pozitia alfabetica. Override de metoda mostenita NU cere `*m:`.
6. Patch review: `git diff --no-index <bak> <editat> > docs/diff_runda<N>_<subiect>.patch`
(exit 1 = normal). Utilizatorul aproba patch-ul; aprobarea conditioneaza COMMIT-ul, nu
write-back-ul.
7. Write-back: `txt2vcx.ps1 -TextFile <vc2> -ProjectRoot <root>` (+ `-AllowComun` pentru tinte
COMUN). Daca binarul e lockat, cere utilizatorului sa inchida sesiunea - nu omori procese.
8. Verificarile obligatorii de dupa write-back (vezi Criterii) - sunt separate de fidelity-check.
9. Teste e2e pe cerinta initiala, cu valorile ei asteptate, pe fluxul real al utilizatorului.
10. Curatenie dupa commit: `powershell -File COMUN\utile\curatenie.ps1`.
## Capcane
- **Membru lipsa din `*<DefinedPropArrayMethod>`**: `txt2vcx` trece fidelity-check-ul, binarul
primeste valoarea, dar VFP nu materializeaza membrul; prima salvare din IDE il sterge tacit.
- **Ordinea alfabetica FoxBin2Prg**: `_` vine DUPA litere (ASCII). Ordinea canonica se citeste din
`<staging>\verify\*.vc2`, nu se deduce.
- **LF izolat**: here-string-urile PowerShell insereaza LF pe liniile noi -> capete de linie
amestecate. `-DryRun` NU prinde asta (compara doar textul, nu CRLF vs LF).
- **Encoding**: antetul zice `CPID="1252"`, dar octetii diacriticelor sunt cp1250; `EF BF BD` e
semnatura coruperii de catre un tool UTF-8.
- **`.??2` poate fi mai vechi decat binarul** (snapshot): fara `git_sync` inainte, editezi text
depasit, iar write-back-ul pierde ce s-a schimbat in IDE.
- **`Column` nu are `When`/`Valid`/`Click`** - se scriu pe `<grid>.<coloana>.<control>`; altfel
prima salvare din IDE le sterge tacit.
- Nu rula *Project > Build* ca sa recompilezi binarul vizat: rescrie antetul tuturor librariilor.
- `txt2vcx.ps1`/`vfp_symbols.ps1` fara `-ProjectRoot` cad pe `ROAACNPRO`. Paseaza-le mereu explicit.
## Criterii de acceptare si dovada
Fara dovada da/nu de mai jos, editarea NU se raporteaza ca terminata. "Arata bine" nu e dovada.
1. Cens octeti >= 0x80 identic inainte/dupa (secventa, nu doar totalul):
`od -An -tx1 <fisier> | tr ' ' '\n' | grep -v '^$' | awk '$1>"7f"' | sort | uniq -c`
-> secventa identica; cauta explicit `EF BF BD` (trebuie absent).
2. Zero LF izolat: `perl -e 'local $/; open F,"<:raw",$ARGV[0]; $d=<F>; $crlf=()=$d=~/\r\n/g; $lf=()=$d=~/\n/g; print $lf-$crlf' <fisier>` -> 0.
3. Fidelity-check al write-back-ului: `txt2vcx.ps1` a reusit, mtime nou pe `.vcx`+`.vct`.
4. Membrul nou prezent: `grep -n '\*[pm]: <nume>' <f>.vc2` -> o linie, la pozitia alfabetica.
5. Round-trip binar: `vcx2txt.ps1` ulterior vede binarul la zi (text regenerat identic).
6. Dupa orice sesiune IDE: `git_sync` + compara lista `*p:`/`*m:` cu starea de dinainte.
## Scripturi
- `D:\ROA\UTIL\foxbin2prg\git_sync.ps1`
- `D:\ROA\UTIL\foxbin2prg\vcx2txt.ps1`
- `D:\ROA\UTIL\foxbin2prg\txt2vcx.ps1`
- `D:\ROA\UTIL\foxbin2prg\vfp_symbols.ps1`
## Referinta (doar la nevoie)
- Doar daca ai nevoie de detaliile adanci (tabel de octeti, exemple de capcane), citeste `COMUN\docs\flux-editare-vfp-text.md` si `COMUN\docs\conventie_encoding_cp1252.md`.

162
utile/leaga_skills.ps1 Normal file
View File

@@ -0,0 +1,162 @@
# leaga_skills.ps1 - expune folderul canonic de agent-skills al ROA catre agenti, fara sa dubleze fisiere.
# Creeaza cate un link de director (symlink) pentru fiecare skill din sursa in folderul global
# %USERPROFILE%\.claude\skills. Atat Claude Code cat si opencode citesc %USERPROFILE%\.claude\skills\<nume>\SKILL.md,
# deci un singur set de link-uri serveste ambele gazde.
# Link-urile de director cer Developer Mode pe Windows sau o consola de administrator (mklink /D).
# Utilizare:
# powershell -File COMUN\utile\leaga_skills.ps1 # leaga toate skill-urile
# powershell -File COMUN\utile\leaga_skills.ps1 -DryRun # doar listeaza ce ar face
# powershell -File COMUN\utile\leaga_skills.ps1 -Force # inlocuieste si link-urile care arata in alta parte
param(
[string]$Source = (Join-Path $PSScriptRoot '..\skills'),
[string]$Target = (Join-Path $env:USERPROFILE '.claude\skills'),
[switch]$DryRun,
[switch]$Force
)
$ErrorActionPreference = 'Stop'
function Normalize-Cale {
param([string]$Cale)
if (-not $Cale) { return '' }
([System.IO.Path]::GetFullPath($Cale)).TrimEnd('\')
}
function Ia-TintaLink {
# PowerShell 5.1 da Target ca string, PowerShell 7 ca lista de string-uri
param($Item)
$t = $Item.Target
if ($t -is [array]) { return ($t | Select-Object -First 1) }
return $t
}
function Sterge-Link {
# sterge doar link-ul (reparse point), niciodata continutul din tinta
param([string]$Link)
try {
if (Test-Path -LiteralPath $Link -PathType Container) {
[System.IO.Directory]::Delete($Link, $false)
} else {
Remove-Item -LiteralPath $Link -Force
}
return $true
} catch {
return $false
}
}
function Creeaza-Link {
# intoarce $true la succes; $script:EroareLink primeste mesajul la esec
param([string]$Link, [string]$Dir)
$script:EroareLink = ''
try {
New-Item -ItemType SymbolicLink -Path $Link -Target $Dir -ErrorAction Stop | Out-Null
return $true
} catch {
$script:EroareLink = $_.Exception.Message
}
$r = & cmd /c mklink /D "$Link" "$Dir" 2>&1
if ($LASTEXITCODE -eq 0) { return $true }
$script:EroareLink = (($r | ForEach-Object { [string]$_ }) -join ' ').Trim()
return $false
}
if (-not (Test-Path -LiteralPath $Source -PathType Container)) {
Write-Output ('leaga_skills: nu gasesc folderul sursa ' + $Source + ' - nimic de facut.')
exit 0
}
$skilluri = @(Get-ChildItem -LiteralPath $Source -Directory | Sort-Object Name)
if ($skilluri.Count -eq 0) {
Write-Output ('leaga_skills: ' + $Source + ' nu contine niciun subfolder de skill - nimic de facut.')
exit 0
}
if (-not (Test-Path -LiteralPath $Target -PathType Container)) {
if ($DryRun) {
Write-Output ('DryRun - as crea folderul tinta ' + $Target)
} else {
New-Item -ItemType Directory -Force -Path $Target | Out-Null
Write-Output ('creat folderul tinta ' + $Target)
}
}
$creat = 0; $ok = 0; $atentionat = 0; $anuntPrivilegii = $false
foreach ($s in $skilluri) {
$dir = $s.FullName
$link = Join-Path $Target $s.Name
$item = Get-Item -LiteralPath $link -Force -ErrorAction SilentlyContinue
if (-not $item) {
if ($DryRun) {
Write-Output (' ar crea ' + $s.Name + ' -> ' + $dir)
$creat++
continue
}
if (Creeaza-Link -Link $link -Dir $dir) {
Write-Output (' creat ' + $s.Name + ' -> ' + $dir)
$creat++
} else {
Write-Output (' EROARE ' + $s.Name + ' - ' + $script:EroareLink)
$atentionat++
if (-not $anuntPrivilegii) {
Write-Output ' Link-urile de director cer Windows Developer Mode (Settings > Privacy & security > For developers)'
Write-Output ' sau o consola PowerShell pornita ca administrator. Alternativ, copiaza manual folderele de skill.'
$anuntPrivilegii = $true
}
}
continue
}
if ($item.LinkType) {
$actual = Normalize-Cale (Ia-TintaLink $item)
if ($actual -eq (Normalize-Cale $dir)) {
Write-Output (' ok ' + $s.Name + ' (link deja corect)')
$ok++
continue
}
Write-Output ('Atentie: ' + $link + ' arata catre ' + $actual + ', nu catre ' + $dir)
if (-not $Force) {
Write-Output (' sarit ' + $s.Name + ' - ruleaza cu -Force ca sa il inlocuiesc.')
$atentionat++
continue
}
if ($DryRun) {
Write-Output (' ar lega ' + $s.Name + ' (inlocuieste link-ul gresit) -> ' + $dir)
$creat++
continue
}
if (-not (Sterge-Link -Link $link)) {
Write-Output (' EROARE ' + $s.Name + ' - nu pot sterge link-ul vechi.')
$atentionat++
continue
}
if (Creeaza-Link -Link $link -Dir $dir) {
Write-Output (' refacut ' + $s.Name + ' -> ' + $dir)
$creat++
} else {
Write-Output (' EROARE ' + $s.Name + ' - ' + $script:EroareLink)
$atentionat++
if (-not $anuntPrivilegii) {
Write-Output ' Link-urile de director cer Windows Developer Mode (Settings > Privacy & security > For developers)'
Write-Output ' sau o consola PowerShell pornita ca administrator.'
$anuntPrivilegii = $true
}
}
continue
}
# nume ocupat de un director/fisier real - nu il atingem niciodata
$tip = 'fisier'
if ($item.PSIsContainer) { $tip = 'director real' }
Write-Output (' sarit ' + $s.Name + ' - ' + $tip + ' exista deja la ' + $link + '; nu il sterg si nu il suprascriu.')
$atentionat++
}
Write-Output ''
if ($DryRun) {
Write-Output ('leaga_skills (DryRun): ' + $creat + ' de creat, ' + $ok + ' ok, ' + $atentionat + ' atentionari')
} else {
Write-Output ('leaga_skills: ' + $creat + ' create, ' + $ok + ' ok, ' + $atentionat + ' atentionari')
}
exit 0