devmethod-ai 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/.agents/skills/design-to-code/SKILL.md +7 -1
  2. package/.agents/skills/design-to-code/references/visual-creation.md +37 -0
  3. package/.agents/skills/project-foundation/SKILL.md +1 -1
  4. package/.agents/skills/project-foundation/assets/PROJECT_PROFILE.md +1 -0
  5. package/.agents/skills/project-foundation/references/mission-context.md +10 -0
  6. package/.agents/skills/project-foundation/references/operating-commands.md +14 -2
  7. package/.agents/skills/scoped-delivery/SKILL.md +2 -0
  8. package/.agents/skills/scoped-delivery/assets/MISSION.md +18 -1
  9. package/COMPATIBILITY.md +1 -1
  10. package/README.md +31 -12
  11. package/docs/MISSIONS.md +2 -0
  12. package/docs/RELEASE-0.1.0.md +14 -3
  13. package/docs/RELEASE-0.2.0.md +37 -0
  14. package/docs/ROADMAP.md +2 -0
  15. package/docs/VISUAL-CREATION-PROPOSAL.md +21 -0
  16. package/docs/VISUAL-WORKFLOW.md +75 -0
  17. package/docs/images/devmethod-delivery.svg +10 -0
  18. package/docs/images/devmethod-flow.svg +13 -26
  19. package/docs/media/from-zero/README.md +17 -0
  20. package/docs/media/from-zero/devmethod-demo.fr.srt +59 -0
  21. package/docs/media/from-zero/scenes.json +50 -0
  22. package/docs/media/full-chain-4k/README.md +37 -0
  23. package/docs/media/full-chain-4k/devmethod-chaine-complete.fr.srt +435 -0
  24. package/docs/media/full-chain-4k/scenes.json +173 -0
  25. package/docs/media/full-chain-4k/transcripts.fr.md +315 -0
  26. package/docs/media/visual-chain/README.md +44 -0
  27. package/docs/media/visual-chain/assets/add-book-v1.png +0 -0
  28. package/docs/media/visual-chain/assets/completed-v1.png +0 -0
  29. package/docs/media/visual-chain/devmethod-du-besoin-au-produit.fr.srt +147 -0
  30. package/docs/media/visual-chain/execution.fr.md +30 -0
  31. package/docs/media/visual-chain/image-prompts.json +9 -0
  32. package/docs/media/visual-chain/reference-hashes.json +5 -0
  33. package/docs/media/visual-chain/scenes.json +232 -0
  34. package/docs/media/visual-chain/video-preview.jpg +0 -0
  35. package/docs/missions/visual-workflow.md +46 -0
  36. package/examples/clair-from-zero/AGENT-EVALUATION.md +15 -0
  37. package/examples/clair-from-zero/MISSION.md +41 -0
  38. package/examples/clair-from-zero/README.md +24 -0
  39. package/examples/clair-from-zero/app/app.mjs +69 -0
  40. package/examples/clair-from-zero/app/domain.mjs +30 -0
  41. package/examples/clair-from-zero/app/index.html +20 -0
  42. package/examples/clair-from-zero/app/storage.mjs +4 -0
  43. package/examples/clair-from-zero/app/styles.css +128 -0
  44. package/examples/clair-from-zero/browser-check.cjs +2 -0
  45. package/examples/clair-from-zero/tests/domain.test.mjs +37 -0
  46. package/examples/visual-pilot/README.md +11 -0
  47. package/examples/visual-pilot/app/app.js +10 -0
  48. package/examples/visual-pilot/app/index.html +1 -0
  49. package/examples/visual-pilot/app/reference.png +0 -0
  50. package/examples/visual-pilot/app/style.css +3 -0
  51. package/examples/visual-pilot/browser-check.cjs +3 -0
  52. package/examples/visual-pilot/desktop-actual.png +0 -0
  53. package/examples/visual-pilot/directions-prompt.txt +1 -0
  54. package/examples/visual-pilot/directions-v1.png +0 -0
  55. package/examples/visual-pilot/editorial-mockup-prompt.txt +1 -0
  56. package/examples/visual-pilot/editorial-mockup-v1.png +0 -0
  57. package/examples/visual-pilot/mismatch-probe.png +0 -0
  58. package/examples/visual-pilot/mobile-actual.png +0 -0
  59. package/examples/visual-pilot/quick-filter/AGENT-RESULT.md +25 -0
  60. package/examples/visual-pilot/quick-filter/baseline.log +42 -0
  61. package/examples/visual-pilot/quick-filter/filter.mjs +3 -0
  62. package/examples/visual-pilot/quick-filter/filter.test.mjs +9 -0
  63. package/package.json +2 -2
  64. package/scripts/media/demo-actions.cjs +16 -0
  65. package/scripts/media/encode-demo.py +22 -0
  66. package/scripts/media/record-demo.cjs +30 -0
  67. package/scripts/media/visual-short/check.cjs +1 -0
  68. package/scripts/media/visual-short/encode.py +16 -0
  69. package/scripts/media/visual-short/record.cjs +4 -0
  70. package/scripts/media/visual-short/story.py +34 -0
  71. package/scripts/package-smoke.mjs +2 -2
@@ -0,0 +1 @@
1
+ const fs=require('fs'),assert=require('node:assert/strict');const{chromium}=require(process.env.PLAYWRIGHT_MODULE || 'playwright');const r='/private/tmp/devmethod-shortfilm';(async()=>{const b=await chromium.launch({channel:'chrome',headless:true});const p=await b.newPage({viewport:{width:1440,height:1000}}),errs=[];p.on('pageerror',e=>errs.push(e.message));await p.goto('http://localhost:8768/app/');assert.equal(await p.locator('.book').count(),3);assert.equal(await p.locator('#storage-alert').isVisible(),false);await p.locator('.book').first().getByRole('button',{name:'Marquer terminé'}).click();assert.equal(await p.evaluate(()=>document.activeElement.textContent),'Rouvrir');await p.getByRole('button',{name:'Terminés',exact:true}).click();await p.screenshot({path:r+'/desktop.png'});await p.getByRole('button',{name:'Rouvrir'}).click();assert.equal(await p.locator('#empty').isVisible(),true);await p.getByRole('button',{name:'Tous',exact:true}).click();await p.getByRole('button',{name:/Ajouter un livre/}).click();await p.getByLabel('Titre',{exact:true}).fill('Les heures claires');await p.getByLabel('Auteur',{exact:true}).fill('Camille Laurent');await p.screenshot({path:r+'/modal.png'});await p.getByRole('button',{name:'Ajouter',exact:true}).click();assert.equal(await p.locator('.book').count(),4);await p.reload();assert.equal(await p.locator('.book').count(),4);await p.setViewportSize({width:390,height:844});await p.screenshot({path:r+'/mobile.png',fullPage:true});assert(await p.evaluate(()=>document.documentElement.scrollWidth<=innerWidth));assert.equal(errs.length,0);await p.evaluate(()=>localStorage.setItem('lisiere.books.v1','broken'));await p.reload();assert(await p.locator('#storage-alert').isVisible());await p.locator('.book').first().getByRole('button').click();assert.equal(await p.evaluate(()=>localStorage.getItem('lisiere.books.v1')),'broken');console.log('PASS: initial UI, filter, status/focus, empty state, modal, add, reload, mobile, malformed storage preserved; zero page errors.');await b.close()})();
@@ -0,0 +1,16 @@
1
+ import pathlib,json,subprocess,re
2
+ r=pathlib.Path('/private/tmp/devmethod-shortfilm');o=pathlib.Path('docs/media/visual-chain');s=json.loads((r/'scenes.json').read_text());t=json.loads((r/'times.json').read_text());base=t[0]['start'];total=t[-1]['start']-base
3
+ subs=[];concat=[]
4
+ def run(a):
5
+ p=subprocess.run(a,stdout=subprocess.DEVNULL,stderr=subprocess.PIPE)
6
+ if p.returncode:raise RuntimeError(p.stderr.decode()[-1500:])
7
+ def stamp(v):
8
+ m=round(v*1000);return f'{m//3600000:02}:{m//60000%60:02}:{m//1000%60:02},{m%1000:03}'
9
+ for i,scene in enumerate(s):
10
+ duration=t[i+1]['start']-t[i]['start'];run(['ffmpeg','-y','-i',str(r/f'audio/{i}.aiff'),'-af','apad','-t',str(duration),'-ar','48000','-c:a','pcm_s16le',str(r/f'audio/{i}.wav')]);concat.append(f"file '{r}/audio/{i}.wav'")
11
+ parts=re.split(r'(?<=[.!?])\s+',scene['voice']);cursor=t[i]['start']-base;spoken=float(subprocess.check_output(['ffprobe','-v','error','-show_entries','format=duration','-of','csv=p=0',str(r/f'audio/{i}.aiff')]))
12
+ for part in parts:
13
+ length=spoken*len(part)/sum(map(len,parts));subs.append(f'{len(subs)+1}\n{stamp(cursor)} --> {stamp(cursor+length)}\n{part}\n');cursor+=length
14
+ (r/'audio-concat.txt').write_text('\n'.join(concat));(o/'devmethod-du-besoin-au-produit.fr.srt').write_text('\n'.join(subs))
15
+ vf=f"[0:v]trim=start={base}:end={t[-1]['start']},setpts=PTS-STARTPTS,fps=25,format=yuv420p[v]"
16
+ run(['ffmpeg','-y','-i',str(r/'film.webm'),'-f','concat','-safe','0','-i',str(r/'audio-concat.txt'),'-i',str(o/'devmethod-du-besoin-au-produit.fr.srt'),'-filter_complex',vf,'-map','[v]','-map','1:a','-map','2:0','-t',str(total),'-c:v','libx264','-threads','4','-preset','veryfast','-crf','20','-c:a','aac','-b:a','192k','-c:s','mov_text','-metadata:s:a:0','language=fra','-metadata:s:s:0','language=fra','-movflags','+faststart',str(o/'devmethod-du-besoin-au-produit-4k.fr.mp4')]);print('FINAL',total,flush=True)
@@ -0,0 +1,4 @@
1
+ const fs=require('fs');const{chromium}=require(process.env.PLAYWRIGHT_MODULE || 'playwright');const r='/private/tmp/devmethod-shortfilm',repo=process.cwd();const onlyApp=process.argv.includes('--only-app');const scenes=JSON.parse(fs.readFileSync(r+'/scenes.json'));const esc=s=>s.replaceAll('&','&amp;').replaceAll('<','&lt;').replaceAll('>','&gt;');const assetPaths={directions:repo+'/examples/visual-pilot/directions-v1.png',master:repo+'/examples/visual-pilot/editorial-mockup-v1.png',add:repo+'/docs/media/visual-chain/assets/add-book-v1.png',completed:repo+'/docs/media/visual-chain/assets/completed-v1.png',desktop:r+'/desktop.png',mobile:r+'/mobile.png'};const images={};for(const[k,v]of Object.entries(assetPaths))if(fs.existsSync(v))images[k]='data:image/png;base64,'+fs.readFileSync(v).toString('base64');
2
+ const css=`*{box-sizing:border-box}html,body{margin:0;background:#0e1d21;overflow:hidden;color:#f6f0e5;font-family:Arial}.stage{position:relative;width:1920px;height:1080px;transform:scale(2);transform-origin:0 0;overflow:hidden;background:radial-gradient(ellipse at 90% 100%,#25433f,#0e1d21 70%)}header{position:absolute;left:80px;right:80px;top:38px;display:flex;justify-content:space-between;color:#b0c6bf;font-size:20px;letter-spacing:2px}h1{position:absolute;left:80px;top:85px;font:64px/1.05 Georgia;font-weight:400;letter-spacing:-1.5px;margin:0}footer{position:absolute;left:80px;bottom:28px;font-size:16px;color:#a0b8b1}.counter{position:absolute;right:80px;bottom:28px;font-size:17px;color:#a0b8b1}.progress{position:absolute;bottom:0;left:0;height:4px;width:100%;transform:scaleX(0);transform-origin:left;background:#dc9972;animation:progress var(--duration) linear forwards}@keyframes progress{to{transform:scaleX(1)}}.command{background:#183137;border:1px solid #45625f;border-radius:16px;padding:28px;box-shadow:0 20px 60px #0002}.command small{font-size:16px;color:#cfad8d;letter-spacing:2px}.cmd{font:25px/1.45 Menlo,monospace;color:#f1b98e;white-space:pre-wrap;min-height:72px;margin:18px 0}.request{font-size:25px;line-height:1.4;color:#ecf1e8}.side{position:absolute;left:80px;top:250px;width:500px}.out{margin-top:28px;font-size:27px;line-height:1.5;color:#c6d7cc;animation:enter .5s 1.2s both}.visual{position:absolute;left:650px;top:215px;width:1190px;height:780px;display:flex;align-items:center;justify-content:center;overflow:hidden;border-radius:10px}.visual img{width:100%;height:100%;object-fit:contain;animation:enter .6s .7s both,drift var(--duration) ease-in-out both}.full{left:150px;width:1620px}.full img{max-width:1550px}.mobile img{max-width:440px}.choice{position:absolute;right:70px;bottom:30px;background:#a13a20;border:1px solid #ffd3ae;border-radius:12px;padding:18px 26px;font:30px Georgia;animation:enter .5s 1.8s both}.history{position:absolute;left:150px;bottom:82px;font-size:16px;color:#9fbab2}.central{position:absolute;left:210px;top:280px;width:1500px}.central .cmd{font-size:40px}.central .request{font-size:35px}.central .out{font-size:44px}.hero{position:absolute;left:140px;top:310px;font:110px/1.13 Georgia;max-width:1580px}.hero b{font-weight:400;color:#e9ae82}.sub{font:34px/1.6 Arial;color:#b5d0c2;animation:enter .6s 1s both}.mini{position:absolute;width:480px;height:650px;top:310px;background:#233b3b;border-radius:15px;padding:12px;animation:enter .55s both}.mini img{width:100%;height:100%;object-fit:contain}.mini.one{left:130px}.mini.two{left:720px;animation-delay:1.8s}.mini.three{left:1310px;animation-delay:3.5s}.arrow{position:absolute;top:600px;color:#e8a776;font-size:60px}.derivecmd{position:absolute;left:130px;right:130px;top:200px;font:24px Menlo;color:#f1b98e}.pair{position:absolute;top:245px;left:90px;right:90px;display:flex;gap:35px;height:700px}.pair figure{width:50%;margin:0;border-radius:12px;overflow:hidden;background:#213838;padding:15px;animation:enter .7s both}.pair img{width:100%;height:610px;object-fit:contain;animation:drift var(--duration) ease-in-out}.pair figcaption{font-size:23px;padding-top:20px}.pair figure+figure{animation-delay:1s}.options{position:absolute;left:370px;right:370px;bottom:70px;display:flex;justify-content:space-between;gap:20px}.options span{font:25px Georgia;background:#1e393b;padding:16px 30px;border-radius:8px;animation:enter .5s both}.options span:nth-child(2){animation-delay:1.6s}.options span:nth-child(3){animation-delay:3.2s}iframe{position:absolute;left:80px;top:230px;width:1440px;height:1000px;transform:scale(.77);transform-origin:0 0;border:0;border-radius:10px}aside{position:absolute;left:1290px;top:390px;width:510px}aside small{font-size:20px;color:#e6ae82}aside h2{font:57px/1.12 Georgia}aside p{font:27px/1.5 Arial;color:#c0d7c9}#cursor{position:absolute;width:22px;height:22px;border:3px solid #fff;background:#b95829;border-radius:50%;box-shadow:0 1px 10px #0009;z-index:20;transition:left .35s,top .35s;pointer-events:none;left:100px;top:300px}@keyframes enter{from{opacity:0;transform:translateY(24px)}to{opacity:1;transform:translateY(0)}}@keyframes drift{from{scale:.985}to{scale:1.035}}`;
3
+ function content(s,i){let x='';const command=`<div class="command"><small>COMMANDE DANS CODEX</small><div class="cmd" data-type="${esc(s.command)}"></div><div class="request">${esc(s.request)}</div></div>`;if(s.kind==='intro'||s.kind==='end')x=`<div class="hero">${s.kind==='intro'?'Une idée.<br><b>Un design. Un produit.</b>':'Votre projet,<br><b>étape après étape.</b>'}<p class="sub">${s.kind==='intro'?'Cadrage → design → architecture → réalisation → vérification':'github.com/montassarkhalloufi/DevMethod'}</p></div>`;else if(s.kind==='text')x=`<div class="central">${command}<div class="out">${esc(s.output)}</div></div>`;else if(s.kind==='derive')x=`<div class="derivecmd">${esc(s.command)} — ${esc(s.request)}</div><div class="mini one"><img src="${images.master}"></div><span class="arrow" style="left:640px">→</span><div class="mini two"><img src="${images.add}"></div><div class="mini three"><img src="${images.completed}"></div>`;else if(s.kind==='compare')x=`<div class="pair"><figure><img src="${images.completed}"><figcaption>Image dérivée du master</figcaption></figure><figure><img src="${images.desktop}"><figcaption>Écran réalisé et vérifié</figcaption></figure></div>`;else if(s.kind==='app')x='<iframe src="http://localhost:8768/app/"></iframe><aside><small>APPLICATION RÉELLE</small><h2 id="action">Votre bibliothèque</h2><p id="detail">Les contrôles agissent vraiment.</p></aside><div id="cursor"></div>';else x=(s.command?`<div class="side">${command}</div>`:'')+`<div class="visual ${s.command?'':'full'} ${s.asset==='mobile'?'mobile':''}"><img src="${images[s.asset]}">${s.kind==='choice'?'<div class="choice">Choix réel : A ✓</div>':''}</div>`+(s.kind==='options'?'<div class="options"><span>A · Éditorial</span><span>B · Atelier</span><span>C · Botanique</span></div>':'')+(['options','choice'].includes(s.kind)?'<div class="history">Choix conservé de la session préparatoire</div>':'');return`<style>${css}</style><div class="stage" style="--duration:${s.duration}s"><header><span>DevMethod / Lisière</span><span>${String(i+1).padStart(2,'0')} / ${scenes.length}</span></header><h1>${s.title}</h1>${x}<footer>Commandes types · résultats réels · montage accéléré</footer><span class="counter">4K / FR</span><div class="progress"></div></div>`;}
4
+ (async()=>{const b=await chromium.launch({channel:'chrome',headless:true});const c=await b.newContext({viewport:{width:3840,height:2160},recordVideo:{dir:r+'/raw',size:{width:3840,height:2160}}});const begun=Date.now(),p=await c.newPage(),times=[];await p.goto('http://localhost:8768/app/');await p.evaluate(()=>localStorage.removeItem('lisiere.books.v1'));for(let i=0;i<scenes.length;i++){if(onlyApp&&scenes[i].kind!=='app')continue;const s=scenes[i];await p.setContent(content(s,i));const start=Date.now();times.push({i,start:(start-begun)/1000});console.log('scene',i);await p.evaluate(()=>{document.querySelectorAll('[data-type]').forEach(e=>{const t=e.dataset.type;let n=0;const id=setInterval(()=>{e.textContent=t.slice(0,++n);if(n>=t.length)clearInterval(id)},18)})});if(s.kind==='app'){const f=p.frameLocator('iframe');const click=async loc=>{await loc.hover();await p.waitForTimeout(250);await loc.click();};const mark=async(t,d)=>{await p.locator('#action').evaluate((e,v)=>e.textContent=v,t);await p.locator('#detail').evaluate((e,v)=>e.textContent=v,d)};await f.locator('h1').waitFor();await p.locator('#cursor').evaluate(e=>e.remove());await p.frames().find(frame=>frame.url().includes(':8768/app/')).evaluate(()=>{const dot=document.createElement('div');dot.style.cssText='position:fixed;width:18px;height:18px;border:2px solid white;border-radius:50%;background:#b95829;z-index:999999;pointer-events:none;transform:translate(-50%,-50%);display:none';document.body.append(dot);document.addEventListener('pointermove',e=>{dot.style.display='block';dot.style.left=e.clientX+'px';dot.style.top=e.clientY+'px'})});await p.waitForTimeout(1500);await mark('Filtrer','Retrouver les livres à commencer.');await click(f.getByRole('button',{name:'À lire',exact:true}));await p.waitForTimeout(1800);await click(f.getByRole('button',{name:'Tous',exact:true}));await mark('Ajouter','Un vrai formulaire, une nouvelle lecture.');await click(f.getByRole('button',{name:/Ajouter un livre/}).first());await f.getByLabel('Titre',{exact:true}).fill('Les heures claires');await f.getByLabel('Auteur',{exact:true}).fill('Camille Laurent');await p.waitForTimeout(1600);await click(f.getByRole('button',{name:'Ajouter',exact:true}));await mark('Faire évoluer','À lire → En cours → Terminé');await click(f.locator('.book').filter({hasText:'Les heures claires'}).getByRole('button',{name:'Commencer',exact:true}));await p.waitForTimeout(1500);await click(f.locator('.book').filter({hasText:'Les heures claires'}).getByRole('button',{name:'Marquer terminé',exact:true}));await click(f.getByRole('button',{name:'Terminés',exact:true}));await p.waitForTimeout(1500);await mark('Retrouver','Les données restent après rechargement.');await p.locator('iframe').evaluate(e=>e.setAttribute("src",e.getAttribute("src")));await f.locator('h1').waitFor();if(await f.locator('.book').count()!==4||await f.locator('#storage-alert').isVisible())throw Error('Persistence failed in recorded scene');await click(f.getByRole('button',{name:'Terminés',exact:true}));await f.getByRole('heading',{name:'Les heures claires',exact:true}).waitFor();}await p.waitForTimeout(Math.max(0,s.duration*1000-(Date.now()-start)-300));if(['Choisir les technologies','Organiser le code','Relire et corriger'].includes(s.title)||['app','derive','compare'].includes(s.kind))await p.screenshot({path:r+`/qa-${i}.png`});await p.waitForTimeout(Math.max(0,s.duration*1000-(Date.now()-start)));}times.push({i:scenes.length,start:(Date.now()-begun)/1000});const v=p.video();await c.close();fs.copyFileSync(await v.path(),r+(onlyApp?'/app-final.webm':'/film.webm'));fs.writeFileSync(r+(onlyApp?'/app-final-times.json':'/times.json'),JSON.stringify(times));await b.close();})();
@@ -0,0 +1,34 @@
1
+ import json,pathlib,subprocess
2
+ r=pathlib.Path('/private/tmp/devmethod-shortfilm');(r/'audio').mkdir(exist_ok=True)
3
+ S=[]
4
+ def add(kind,title,voice,seconds,command='',request='',asset='',output=''):
5
+ S.append(dict(kind=kind,title=title,voice=voice,duration=seconds,command=command,request=request,asset=asset,output=output))
6
+ add('intro','De l’idée à l’application','Du besoin au design, des choix techniques au produit vérifié. Voici Dev Method sur un projet concret.',6)
7
+ add('text','Décrire le produit','D’abord, décrivez le produit et ce que son utilisateur doit pouvoir faire.',6,'$project-foundation explore','Une bibliothèque personnelle pour suivre mes lectures.',output='Ajouter un livre · Suivre sa lecture · Retrouver ses données')
8
+ add('text','Cadrer une première version','Frame fixe le périmètre : une bibliothèque personnelle, trois statuts, et une sauvegarde sur cet appareil.',7,'$project-foundation frame','Définis une première version utile et ses limites.',output='Ajouter · Filtrer · Changer de statut · Conserver les livres localement')
9
+ add('image','Demander trois directions','Avec design, demandez trois directions pour le même écran.',6,'$project-foundation design','Propose trois directions visuelles comparables.','directions')
10
+ add('options','Comparer, puis choisir','Éditorial, Atelier ou Botanique. Même contenu, trois personnalités visuelles.',6,asset='directions')
11
+ add('choice','Choisir A — Éditorial','Ici, le choix est A. Codex développe cette direction en master screen.',7,'$project-foundation design','Je choisis A. Finalise le master screen.','master')
12
+ add('image','Le master fixe la référence','Le master précise la typographie, les couleurs, les cartes et les boutons. Il est approuvé avant la suite.',8,asset='master')
13
+ add('derive','Décliner le master','La commande suivante reste design : génère les écrans à partir de ce master.',7,'$project-foundation design','Décline ce master : bibliothèque, ajout et lectures terminées.','master')
14
+ add('image','Écran 02 — Ajouter un livre','Voici le formulaire d’ajout, généré avec le master comme référence visuelle.',6,asset='add')
15
+ add('image','Écran 03 — Lectures terminées','Puis la vue des lectures terminées. La même direction accompagne un autre état du produit.',7,asset='completed')
16
+ add('text','Choisir les technologies','Architecture compare les options selon les besoins, le coût et la maintenance. Pour ce petit prototype local, HTML, CSS et JavaScript suffisent.',9,'$project-foundation architecture','Compare les options techniques adaptées à ce périmètre.',output='Prototype retenu : HTML + CSS + JavaScript · Stockage local au navigateur')
17
+ add('text','Organiser le code','Les règles des livres restent séparées de l’interface. Les données sauvegardées sont validées. Une synchronisation entre appareils demanderait une nouvelle décision.',9,'$project-foundation architecture','Définis les responsabilités, les risques et les compromis.',output='Interface ↔ Règles métier ↔ Sauvegarde locale · Limite : un appareil')
18
+ add('text','Préparer la réalisation','Le plan relie ces écrans aux actions à réaliser et aux vérifications attendues.',6,'$project-foundation plan','Prépare une première tranche à partir des écrans.',output='Bibliothèque + filtres → Formulaire → Statuts + sauvegarde')
19
+ add('text','Vérifier que la tranche est prête','Ready vérifie le périmètre, les dépendances et les critères de réussite avant de coder.',6,'$project-foundation ready VISUAL-1','Vérifie que cette tranche est prête.',output='Références disponibles · Interactions définies · Tests attendus')
20
+ add('text','Transformer les images en code','Implement construit l’interface et ses vrais comportements à partir des références.',6,'$project-foundation implement VISUAL-1','Implémente les écrans et leurs interactions.',output='Nouvelle implémentation à partir du master et des images dérivées')
21
+ add('app','L’application fonctionne','Passons au résultat. On filtre la bibliothèque. On ajoute un livre. On commence sa lecture, puis on le marque terminé. Après rechargement, les données sont toujours présentes.',20)
22
+ add('image','Aussi sur mobile','Sur mobile, les mêmes actions restent accessibles dans une disposition adaptée.',6,asset='mobile')
23
+ add('text','Relire et corriger','Review examine le code et les risques. Ici, la revue a conduit à corriger une alerte masquée et le maintien du focus.',8,'$project-foundation review VISUAL-1','Relis la tranche et signale les défauts à corriger.',output='Corrections réelles : affichage de l’alerte · Focus après changement de statut')
24
+ add('text','Vérifier le résultat','Verify contrôle les parcours utiles et les résultats des tests.',6,'$project-foundation verify VISUAL-1','Vérifie les parcours, les erreurs et le rendu.',output='6 tests Node réussis · Parcours Chrome · Sauvegarde · Mobile')
25
+ add('compare','Comparer au design','On compare ensuite le navigateur aux références, puis on corrige les écarts importants.',7,asset='desktop')
26
+ add('text','Livrer une tranche terminée','La livraison locale conserve le résultat et son état de reprise.',6,'$project-foundation integrate VISUAL-1','Consigne la livraison locale et la reprise.',output='Prototype local · Parcours vérifiés · Références conservées')
27
+ add('text','Reprendre sans repartir de zéro','Handoff conserve les décisions, les preuves et les limites. Next retrouve ensuite la suite pertinente, ou constate que le périmètre est terminé.',8,'$project-foundation handoff','Prépare un point de reprise fidèle à la livraison.',output='Décisions + résultat + limites → $project-foundation next')
28
+ add('end','Votre idée. Une chaîne visible.','Découvrez Dev Method sur GitHub et essayez cette chaîne sur votre projet.',6)
29
+ for i,s in enumerate(S):
30
+ p=r/'audio'/f'{i}.txt';p.write_text(s['voice'].replace('GitHub','guit-hub'))
31
+ subprocess.run(['say','-v','Thomas','-r','165','-f',str(p),'-o',str(r/'audio'/f'{i}.aiff')],check=True)
32
+ d=float(subprocess.check_output(['ffprobe','-v','error','-show_entries','format=duration','-of','csv=p=0',str(r/'audio'/f'{i}.aiff')]))
33
+ s['duration']=max(s['duration'],d+0.7)
34
+ (r/'scenes.json').write_text(json.dumps(S,ensure_ascii=False,indent=2));print('duration',sum(s['duration'] for s in S))
@@ -15,7 +15,7 @@ try {
15
15
  run('tar', ['-xzf', '-'], root, 0, fs.readFileSync(path.resolve(archive)));
16
16
  const pkg = path.join(root, 'package'); const cli = path.join(pkg, 'dist/cli.js');
17
17
  const call = (args, expected = 0) => run(process.execPath, [cli, ...args], root, expected);
18
- assert.equal(JSON.parse(fs.readFileSync(path.join(pkg, 'package.json'))).version, '0.1.0');
18
+ assert.equal(JSON.parse(fs.readFileSync(path.join(pkg, 'package.json'))).version, '0.2.0');
19
19
  run(process.execPath, ['scripts/check-docs.mjs'], pkg);
20
20
  for (const testFile of [...fs.readdirSync(path.join(pkg, 'examples/pocket-tasks/tests')).filter(f => f.endsWith('.test.mjs')).map(f => `examples/pocket-tasks/tests/${f}`), 'evaluation/greenfield/acceptance.test.mjs', 'evaluation/greenfield/security.test.mjs']) {
21
21
  run(process.execPath, ['--test', testFile], pkg);
@@ -60,5 +60,5 @@ try {
60
60
  assert.deepEqual([fs.readFileSync(customized), fs.readFileSync(profile)], before);
61
61
  console.log('Actual legacy tarball: local/upstream conflict detected and filled profile/custom skill preserved.');
62
62
  }
63
- console.log('Packed 0.1.0: three host installs, subset, customization preservation, mission/context/staleness/planning and documentation links passed. No native host execution.');
63
+ console.log('Packed 0.2.0: three host installs, subset, customization preservation, mission/context/staleness/planning and documentation links passed. No native host execution.');
64
64
  } finally { fs.rmSync(root, { recursive: true, force: true }); }