@lssm/bundle.contractspec-workspace 0.0.0-canary-20251213172311

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,76 @@
1
+ # @lssm/bundle.contractspec-workspace
2
+
3
+ Reusable use-cases and services for ContractSpec workspace operations.
4
+
5
+ ## Purpose
6
+
7
+ This bundle provides platform-agnostic services that can be used by:
8
+
9
+ - CLI tools (`@lssm/app.cli-contracts`)
10
+ - Web applications
11
+ - VS Code extensions
12
+ - API servers
13
+
14
+ ## Architecture
15
+
16
+ ```
17
+ bundle.contractspec-workspace
18
+ ├── services/ # Use-case implementations
19
+ │ ├── build.ts # Build deterministic artifacts from specs (templates-first)
20
+ │ ├── validate.ts # Validate spec structure (and, later, implementation checks)
21
+ │ ├── diff.ts # Compare specs (semantic diff)
22
+ │ ├── deps.ts # Analyze dependencies
23
+ │ ├── list.ts # Discover specs by glob
24
+ │ └── config.ts # Load + merge workspace config (.contractsrc.json)
25
+ │ ├── sync.ts # Sync all specs (validate/build across a workspace)
26
+ │ ├── watch.ts # Watch specs and trigger validate/build
27
+ │ ├── clean.ts # Safe-by-default cleanup of generated artifacts
28
+ │ ├── test.ts # Run TestSpec scenarios (pure runner wrapper)
29
+ │ └── regenerator.ts # Regenerator service wrapper (no module loading)
30
+ ├── adapters/ # Runtime adapters (Node defaults)
31
+ │ ├── fs.ts # Filesystem operations
32
+ │ ├── git.ts # Git operations
33
+ │ ├── watcher.ts # File watching
34
+ │ ├── ai.ts # AI providers
35
+ │ └── logger.ts # Logging/progress
36
+ └── ports/ # Adapter interfaces
37
+ └── index.ts # Port type definitions
38
+ ```
39
+
40
+ ## Design Principles
41
+
42
+ - **Adapter pattern**: All I/O goes through explicit ports/adapters
43
+ - **Testable**: Services can be tested with mock adapters
44
+ - **Reusable**: Same services work across CLI, web, and extensions
45
+ - **No CLI dependencies**: No `chalk`, `ora`, `commander`, or `inquirer`
46
+
47
+ ## Usage
48
+
49
+ ```typescript
50
+ import {
51
+ createNodeAdapters,
52
+ loadWorkspaceConfig,
53
+ buildSpec,
54
+ } from '@lssm/bundle.contractspec-workspace';
55
+
56
+ // Create adapters for Node.js runtime
57
+ const adapters = createNodeAdapters();
58
+
59
+ // Load workspace config (or use defaults)
60
+ const config = await loadWorkspaceConfig(adapters.fs);
61
+
62
+ // Build deterministic artifacts from a spec (templates-first)
63
+ const result = await buildSpec(
64
+ './my-spec.contracts.ts',
65
+ { fs: adapters.fs, logger: adapters.logger },
66
+ config
67
+ );
68
+ ```
69
+
70
+ ## Notes
71
+
72
+ - `sync` / `watch` accept optional overrides so CLI (or an extension) can inject
73
+ richer build/validate behavior while reusing the deterministic orchestration.
74
+ - `test` and `regenerator` deliberately avoid TypeScript module loading; callers
75
+ pass already-loaded specs/contexts/rules/sinks.
76
+
@@ -0,0 +1 @@
1
+ import{anthropic as e}from"@ai-sdk/anthropic";import{openai as t}from"@ai-sdk/openai";import{ollama as n}from"ollama-ai-provider";import{generateObject as r,generateText as i,streamText as a}from"ai";function o(e){return{async validateProvider(e){try{let{aiProvider:t}=e;return t===`ollama`?{success:!0}:t===`claude`&&!process.env.ANTHROPIC_API_KEY?{success:!1,error:`ANTHROPIC_API_KEY environment variable not set`}:t===`openai`&&!process.env.OPENAI_API_KEY?{success:!1,error:`OPENAI_API_KEY environment variable not set`}:{success:!0}}catch(e){return{success:!1,error:e instanceof Error?e.message:String(e)}}},async generateText(t){return{text:(await i({model:s(e),prompt:t.prompt,system:t.systemPrompt})).text}},async generateStructured(t){return{object:(await r({model:s(e),schema:t.schema,prompt:t.prompt,system:t.systemPrompt})).object}},async streamText(t,n){let r=await a({model:s(e),prompt:t.prompt,system:t.systemPrompt}),i=``;for await(let e of r.textStream)i+=e,n(e);return i}}}function s(r){let{aiProvider:i,aiModel:a,customEndpoint:o}=r;switch(i){case`claude`:return e(a??`claude-3-5-sonnet-20241022`);case`openai`:return t(a??`gpt-4o`);case`ollama`:return n(a??`codellama`);case`custom`:if(!o)throw Error(`Custom endpoint required. Set customEndpoint in config or CONTRACTSPEC_LLM_ENDPOINT env var`);return t(a??`default`);default:throw Error(`Unknown AI provider: ${i}`)}}export{o as createNodeAiAdapter};
@@ -0,0 +1 @@
1
+ import{createNodeFsAdapter as e}from"./fs.js";import{createNodeGitAdapter as t}from"./git.js";import{createNodeWatcherAdapter as n}from"./watcher.js";import{createNodeAiAdapter as r}from"./ai.js";import{createConsoleLoggerAdapter as i,createNoopLoggerAdapter as a}from"./logger.js";function o(o={}){let{cwd:s,config:c,silent:l}=o,u=c??{aiProvider:`claude`,agentMode:`simple`,outputDir:`./src`,conventions:{operations:`interactions/commands|queries`,events:`events`,presentations:`presentations`,forms:`forms`},defaultOwners:[],defaultTags:[]};return{fs:e(s),git:t(s),watcher:n(s),ai:r(u),logger:l?a():i()}}export{o as createNodeAdapters};
@@ -0,0 +1 @@
1
+ import{access as e,mkdir as t,readFile as n,rm as r,stat as i,writeFile as a}from"node:fs/promises";import{basename as o,dirname as s,isAbsolute as c,join as l,relative as u,resolve as d}from"node:path";import{glob as f}from"glob";const p=[`**/*.contracts.ts`,`**/*.event.ts`,`**/*.presentation.ts`,`**/*.workflow.ts`,`**/*.data-view.ts`,`**/*.migration.ts`,`**/*.telemetry.ts`,`**/*.experiment.ts`,`**/*.app-config.ts`,`**/*.integration.ts`,`**/*.knowledge.ts`],m=[`node_modules/**`,`dist/**`,`.turbo/**`];function h(h){let g=h??process.cwd();return{async exists(t){try{return await e(_(t)),!0}catch{return!1}},async readFile(e){return n(_(e),`utf-8`)},async writeFile(e,n){let r=_(e);await t(s(r),{recursive:!0}),await a(r,n,`utf-8`)},async remove(e){await r(_(e),{recursive:!0,force:!0})},async stat(e){let t=await i(_(e));return{size:t.size,isFile:t.isFile(),isDirectory:t.isDirectory(),mtime:t.mtime}},async mkdir(e){await t(_(e),{recursive:!0})},async glob(e){let t=e.patterns??(e.pattern?[e.pattern]:p),n=e.ignore??m,r=[];for(let e of t){let t=await f(e,{cwd:g,ignore:n});r.push(...t)}return Array.from(new Set(r)).sort((e,t)=>e.localeCompare(t))},resolve(...e){let[t,...n]=e;return t?d(g,t,...n):g},dirname(e){return s(e)},basename(e){return o(e)},join(...e){return l(...e)},relative(e,t){return u(e,t)}};function _(e){return c(e)?e:d(g,e)}}export{h as createNodeFsAdapter};
@@ -0,0 +1 @@
1
+ import{access as e}from"node:fs/promises";import{resolve as t}from"node:path";import{execSync as n}from"node:child_process";function r(r){let i=r??process.cwd();return{async showFile(e,t){try{return n(`git show ${e}:${t}`,{cwd:i,encoding:`utf-8`,stdio:[`ignore`,`pipe`,`pipe`]})}catch(n){throw Error(`Could not load ${t} at ref ${e}: ${n instanceof Error?n.message:String(n)}`)}},async clean(e){let t=[];e?.force&&t.push(`-f`),e?.directories&&t.push(`-d`),e?.ignored&&t.push(`-x`),e?.dryRun&&t.push(`--dry-run`),n(`git clean ${t.join(` `)}`,{cwd:i,stdio:`inherit`})},async isGitRepo(n){let r=n?t(i,n):i;try{return await e(t(r,`.git`)),!0}catch{return!1}}}}export{r as createNodeGitAdapter};
@@ -0,0 +1 @@
1
+ import{createNodeFsAdapter as e}from"./fs.js";import{createNodeGitAdapter as t}from"./git.js";import{createNodeWatcherAdapter as n}from"./watcher.js";import{createNodeAiAdapter as r}from"./ai.js";import{createConsoleLoggerAdapter as i,createNoopLoggerAdapter as a}from"./logger.js";import{createNodeAdapters as o}from"./factory.js";
@@ -0,0 +1 @@
1
+ function e(){return{debug(e,t){process.env.DEBUG&&console.debug(`[DEBUG] ${e}`,t??``)},info(e,t){console.info(`[INFO] ${e}`,t??``)},warn(e,t){console.warn(`[WARN] ${e}`,t??``)},error(e,t){console.error(`[ERROR] ${e}`,t??``)},createProgress(){return n()}}}function t(){let e=()=>{};return{debug:e,info:e,warn:e,error:e,createProgress:r}}function n(){return{start(e){console.log(`⏳ ${e}`)},update(e){let t=e.current!==void 0&&e.total!==void 0?` (${e.current}/${e.total})`:``;console.log(` ${e.message}${t}`)},succeed(e){console.log(`✅ ${e??`Done`}`)},fail(e){console.error(`❌ ${e??`Failed`}`)},warn(e){console.warn(`⚠️ ${e??`Warning`}`)},stop(){}}}function r(){let e=()=>{};return{start:e,update:e,succeed:e,fail:e,warn:e,stop:e}}export{e as createConsoleLoggerAdapter,t as createNoopLoggerAdapter};
@@ -0,0 +1 @@
1
+ import e from"chokidar";const t=[`node_modules/**`,`dist/**`,`.turbo/**`];function n(n){let r=n??process.cwd();return{watch(n){let i=[],a,o=e.watch(n.pattern,{cwd:r,ignored:n.ignore??t,persistent:!0,ignoreInitial:!0,awaitWriteFinish:{stabilityThreshold:250,pollInterval:50}}),s=e=>{n.debounceMs&&n.debounceMs>0?(clearTimeout(a),a=setTimeout(()=>{i.forEach(t=>t(e))},n.debounceMs)):i.forEach(t=>t(e))};return o.on(`add`,e=>{s({type:`add`,path:e})}),o.on(`change`,e=>{s({type:`change`,path:e})}),o.on(`unlink`,e=>{s({type:`unlink`,path:e})}),{on(e){i.push(e)},async close(){clearTimeout(a),await o.close()}}}}}export{n as createNodeWatcherAdapter};
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ import{createNodeFsAdapter as e}from"./adapters/fs.js";import{createNodeGitAdapter as t}from"./adapters/git.js";import{createNodeWatcherAdapter as n}from"./adapters/watcher.js";import{createNodeAiAdapter as r}from"./adapters/ai.js";import{createConsoleLoggerAdapter as i,createNoopLoggerAdapter as a}from"./adapters/logger.js";import{createNodeAdapters as o}from"./adapters/factory.js";import"./adapters/index.js";import{validateSpec as s,validateSpecs as c}from"./services/validate.js";import{validateImplementationFiles as l}from"./services/validate-implementation.js";import{compareSpecs as u}from"./services/diff.js";import{analyzeDeps as d,exportGraphAsDot as f,getContractNode as p,getGraphStats as m}from"./services/deps.js";import{groupSpecsByType as h,listSpecs as g}from"./services/list.js";import{getApiKey as _,loadWorkspaceConfig as v,mergeWorkspaceConfig as y}from"./services/config.js";import{buildSpec as b}from"./services/build.js";import{syncSpecs as x}from"./services/sync.js";import{watchSpecs as S}from"./services/watch.js";import{cleanArtifacts as C}from"./services/clean.js";import{runTests as w}from"./services/test.js";import{createRegeneratorService as T}from"./services/regenerator.js";import"./services/index.js";export*from"@lssm/module.contractspec-workspace";export{d as analyzeDeps,b as buildSpec,C as cleanArtifacts,u as compareSpecs,i as createConsoleLoggerAdapter,o as createNodeAdapters,r as createNodeAiAdapter,e as createNodeFsAdapter,t as createNodeGitAdapter,n as createNodeWatcherAdapter,a as createNoopLoggerAdapter,T as createRegeneratorService,f as exportGraphAsDot,_ as getApiKey,p as getContractNode,m as getGraphStats,h as groupSpecsByType,g as listSpecs,v as loadWorkspaceConfig,y as mergeWorkspaceConfig,w as runTests,x as syncSpecs,l as validateImplementationFiles,s as validateSpec,c as validateSpecs,S as watchSpecs};
@@ -0,0 +1 @@
1
+ import{generateComponentTemplate as e,generateHandlerTemplate as t,generateTestTemplate as n,inferSpecTypeFromFilePath as r,scanSpecSource as i}from"@lssm/module.contractspec-workspace";async function a(e,t,n,a={}){let{fs:c,logger:l}=t,{targets:u=s(e),outputDir:d=n.outputDir,overwrite:f=!1,dryRun:p=!1}=a,m=await c.readFile(e),h=i(m,e),g=r(e);l.info(`Building from spec: ${e}`,{specType:g});let _=[];for(let t of u)try{let n=await o(t,e,m,h,g,{fs:c,logger:l},d,f,p);_.push(n)}catch(e){_.push({target:t,outputPath:``,success:!1,error:e instanceof Error?e.message:String(e)})}return{specPath:e,specInfo:h,results:_}}async function o(r,i,a,o,s,l,u,d,f){let{fs:p,logger:m}=l,h,g;switch(r){case`handler`:{if(s!==`operation`)return{target:r,outputPath:``,success:!1,skipped:!0,error:`Handler generation only supported for operation specs (got ${s})`};let e=o.kind===`command`||o.kind===`query`?o.kind:`command`;h=t(o.name??`unknown`,e),g=c(i,u,`handlers`,o.name??`unknown`,`.handler.ts`,l.fs);break}case`component`:if(s!==`presentation`)return{target:r,outputPath:``,success:!1,skipped:!0,error:`Component generation only supported for presentation specs (got ${s})`};h=e(o.name??`unknown`,o.description??``),g=c(i,u,`components`,o.name??`unknown`,`.tsx`,l.fs);break;case`test`:{let e=s===`operation`?`handler`:`component`;h=n(o.name??`unknown`,e),g=c(i,u,`__tests__`,o.name??`unknown`,`.test.ts`,l.fs);break}default:return{target:r,outputPath:``,success:!1,error:`Unknown target: ${r}`}}if(await p.exists(g)&&!d)return{target:r,outputPath:g,success:!1,skipped:!0,error:`File already exists (use overwrite option)`};if(f)return m.info(`[dry-run] Would write: ${g}`),{target:r,outputPath:g,success:!0};let _=p.dirname(g);return await p.mkdir(_),await p.writeFile(g,h),m.info(`Generated: ${g}`),{target:r,outputPath:g,success:!0}}function s(e){switch(r(e)){case`operation`:return[`handler`];case`presentation`:return[`component`];default:return[]}}function c(e,t,n,r,i,a){let o=l(r.split(`.`).pop()??`unknown`),s;return s=t.startsWith(`.`)?a.resolve(a.dirname(e),`..`,t,n):a.resolve(t,n),a.join(s,`${o}${i}`)}function l(e){return e.replace(/\./g,`-`).replace(/([a-z])([A-Z])/g,`$1-$2`).toLowerCase()}export{a as buildSpec};
@@ -0,0 +1 @@
1
+ async function e(e,t={}){let{fs:n,logger:r}=e,i=(t.outputDir??`./src`).replace(/\\/g,`/`),a=[`generated/**`,`dist/**`,`.turbo/**`],o=[`${i}/handlers/**/*.handler.ts`,`${i}/handlers/**/*.handler.test.ts`,`${i}/components/**/*.tsx`,`${i}/components/**/*.test.tsx`,`${i}/forms/**/*.form.tsx`,`${i}/forms/**/*.form.test.tsx`,`${i}/**/*.runner.ts`,`${i}/**/*.renderer.tsx`],s=t.generatedOnly?[...a,...o]:[...a,`**/*.generated.ts`,`**/*.generated.js`,`**/*.generated.d.ts`,...o],c=await n.glob({patterns:s,ignore:[`node_modules/**`]}),l=[],u=[];for(let e of c)try{let i=await n.stat(e),a=(Date.now()-i.mtime.getTime())/(1e3*60*60*24);if(typeof t.olderThanDays==`number`&&a<t.olderThanDays){u.push({path:e,reason:`younger_than_${t.olderThanDays}_days`});continue}t.dryRun?r.info(`[dry-run] clean would remove`,{path:e,size:i.size}):(await n.remove(e),r.info(`clean.removed`,{path:e,size:i.size})),l.push({path:e,size:i.size})}catch(t){u.push({path:e,reason:t instanceof Error?t.message:String(t)})}return{removed:l,skipped:u}}export{e as cleanArtifacts};
@@ -0,0 +1 @@
1
+ import{DEFAULT_WORKSPACE_CONFIG as e}from"@lssm/module.contractspec-workspace";import*as t from"zod";const n=t.object({aiProvider:t.enum([`claude`,`openai`,`ollama`,`custom`]).default(`claude`),aiModel:t.string().optional(),agentMode:t.enum([`simple`,`cursor`,`claude-code`,`openai-codex`]).default(`simple`),customEndpoint:t.string().url().nullable().optional(),customApiKey:t.string().nullable().optional(),outputDir:t.string().default(`./src`),conventions:t.object({operations:t.string().default(`interactions/commands|queries`),events:t.string().default(`events`),presentations:t.string().default(`presentations`),forms:t.string().default(`forms`)}),defaultOwners:t.array(t.string()).default([]),defaultTags:t.array(t.string()).default([])});async function r(t,r){let i=t.join(r??`.`,`.contractsrc.json`);if(!await t.exists(i))return e;try{let e=await t.readFile(i),r=JSON.parse(e);return n.parse(r)}catch{return e}}function i(e,t){return{...e,aiProvider:t.provider??process.env.CONTRACTSPEC_AI_PROVIDER??e.aiProvider,aiModel:t.model??process.env.CONTRACTSPEC_AI_MODEL??e.aiModel,agentMode:t.agentMode??process.env.CONTRACTSPEC_AGENT_MODE??e.agentMode,customEndpoint:t.endpoint??process.env.CONTRACTSPEC_LLM_ENDPOINT??e.customEndpoint??void 0,customApiKey:process.env.CONTRACTSPEC_LLM_API_KEY??e.customApiKey??void 0,outputDir:t.outputDir??e.outputDir}}function a(e){switch(e){case`claude`:return process.env.ANTHROPIC_API_KEY;case`openai`:return process.env.OPENAI_API_KEY;case`custom`:return process.env.CONTRACTSPEC_LLM_API_KEY;case`ollama`:return;default:return}}export{a as getApiKey,r as loadWorkspaceConfig,i as mergeWorkspaceConfig};
@@ -0,0 +1 @@
1
+ import{addContractNode as e,buildReverseEdges as t,createContractGraph as n,detectCycles as r,findMissingDependencies as i,parseImportedSpecNames as a,toDot as o}from"@lssm/module.contractspec-workspace";async function s(o,s={}){let{fs:c}=o,l=await c.glob({pattern:s.pattern}),u=n();for(let t of l){let n=await c.readFile(t),r=c.relative(`.`,t),i=n.match(/name:\s*['"]([^'"]+)['"]/);e(u,(i?.[1]?i[1]:c.basename(t).replace(/\.[jt]s$/,``).replace(/\.(contracts|event|presentation|workflow|data-view|migration|telemetry|experiment|app-config|integration|knowledge)$/,``))||`unknown`,r,a(n,t))}t(u);let d=r(u),f=i(u);return{graph:u,total:u.size,cycles:d,missing:f}}function c(e,t){return e.get(t)}function l(e){return o(e)}function u(e){let t=Array.from(e.values()),n=t.filter(e=>e.dependencies.length>0),r=t.filter(e=>e.dependencies.length===0),i=t.filter(e=>e.dependents.length>0),a=t.filter(e=>e.dependents.length===0);return{total:e.size,withDeps:n.length,withoutDeps:r.length,used:i.length,unused:a.length}}export{s as analyzeDeps,l as exportGraphAsDot,c as getContractNode,u as getGraphStats};
@@ -0,0 +1 @@
1
+ import{computeSemanticDiff as e}from"@lssm/module.contractspec-workspace";async function t(t,n,r,i={}){let{fs:a,git:o}=r;if(!await a.exists(t))throw Error(`Spec file not found: ${t}`);let s=await a.readFile(t),c,l;if(i.baseline)c=await o.showFile(i.baseline,t),l=`${i.baseline}:${t}`;else{if(!await a.exists(n))throw Error(`Spec file not found: ${n}`);c=await a.readFile(n),l=n}let u=e(s,t,c,l,{breakingOnly:i.breakingOnly});return{spec1:t,spec2:l,differences:u}}export{t as compareSpecs};
@@ -0,0 +1 @@
1
+ import{validateSpec as e,validateSpecs as t}from"./validate.js";import{validateImplementationFiles as n}from"./validate-implementation.js";import{compareSpecs as r}from"./diff.js";import{analyzeDeps as i,exportGraphAsDot as a,getContractNode as o,getGraphStats as s}from"./deps.js";import{groupSpecsByType as c,listSpecs as l}from"./list.js";import{getApiKey as u,loadWorkspaceConfig as d,mergeWorkspaceConfig as f}from"./config.js";import{buildSpec as p}from"./build.js";import{syncSpecs as m}from"./sync.js";import{watchSpecs as h}from"./watch.js";import{cleanArtifacts as g}from"./clean.js";import{runTests as _}from"./test.js";import{createRegeneratorService as v}from"./regenerator.js";
@@ -0,0 +1 @@
1
+ import{scanSpecSource as e}from"@lssm/module.contractspec-workspace";async function t(t,n={}){let{fs:r}=t,i=await r.glob({pattern:n.pattern}),a=[];for(let t of i){let i=e(await r.readFile(t),t);n.type&&i.specType!==n.type||a.push(i)}return a}function n(e){let t=new Map;for(let n of e){let e=t.get(n.specType)??[];e.push(n),t.set(n.specType,e)}return t}export{n as groupSpecsByType,t as listSpecs};
@@ -0,0 +1 @@
1
+ import{RegeneratorService as e}from"@lssm/lib.contracts/regenerator";function t(t){return new e({contexts:t.contexts,adapters:t.adapters??{},rules:t.rules,sink:t.sink,pollIntervalMs:t.pollIntervalMs,batchDurationMs:t.batchDurationMs})}export{t as createRegeneratorService};
@@ -0,0 +1 @@
1
+ import{validateSpec as e}from"./validate.js";import{buildSpec as t}from"./build.js";async function n(n,r,i={},a){let{fs:o,logger:s}=n,c=await o.glob({pattern:i.pattern}),l=i.outputDirs?.length?i.outputDirs:[void 0],u=[],d=a?.validate??(t=>e(t,{fs:o,logger:s})),f=a?.build??((e,n)=>t(e,{fs:o,logger:s},n?{...r,outputDir:n}:r,{...i.buildOptions??{},outputDir:n}));for(let e of c)for(let t of l){let n={specPath:e,outputDir:t};if(i.validate)try{n.validation=await d(e)}catch(e){n.error={phase:`validate`,message:e instanceof Error?e.message:String(e)},u.push(n);continue}if(i.dryRun)s.info(`[dry-run] syncSpecs skipped build`,{specPath:e,outputDir:t});else try{n.build=await f(e,t)}catch(e){n.error={phase:`build`,message:e instanceof Error?e.message:String(e)},u.push(n);continue}u.push(n)}return{specs:c,runs:u}}export{n as syncSpecs};
@@ -0,0 +1 @@
1
+ import{TestRunner as e}from"@lssm/lib.contracts/tests";async function t(t,n){let r=new e({registry:n}),i=[],a=0,o=0;for(let e of t){let t=await r.run(e);i.push(t),a+=t.passed,o+=t.failed}return{results:i,passed:a,failed:o}}export{t as runTests};
@@ -0,0 +1 @@
1
+ import{scanSpecSource as e}from"@lssm/module.contractspec-workspace";function t(e){return e.replace(/\./g,`-`).replace(/([a-z])([A-Z])/g,`$1-$2`).toLowerCase()}function n(e){return e.split(/[-_.]/).filter(Boolean).map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(``)}async function r(r,i,a,o={}){let{fs:s}=i,c=[],l=[];if(!await s.exists(r))return{valid:!1,errors:[`Spec file not found: ${r}`],warnings:[],expected:{}};let u=e(await s.readFile(r),r),d=u.name??s.basename(r).replace(/\.[jt]s$/,``),f=o.outputDir??a.outputDir??`./src`,p=t(d),m={};if(u.specType===`operation`&&(m.handlerPath=s.join(f,`handlers`,`${p}.handler.ts`),m.handlerTestPath=s.join(f,`handlers`,`${p}.handler.test.ts`)),u.specType===`presentation`&&(m.componentPath=s.join(f,`components`,`${p}.tsx`),m.componentTestPath=s.join(f,`components`,`${p}.test.tsx`)),u.specType===`form`&&(m.formPath=s.join(f,`forms`,`${p}.form.tsx`),m.formTestPath=s.join(f,`forms`,`${p}.form.test.tsx`)),o.checkHandlers&&m.handlerPath)if(!await s.exists(m.handlerPath))c.push(`Missing handler file: ${m.handlerPath}`);else{let e=await s.readFile(m.handlerPath),t=`${n(d.split(`.`).pop()??d)}Spec`,r=/ContractHandler<\s*typeof\s+\w+\s*>/.test(e),i=RegExp(`typeof\\s+${t}\\b`).test(e);r?i||l.push(`Handler ContractHandler typing does not reference expected spec var (${t}): ${m.handlerPath}`):l.push(`Handler does not appear to type itself as ContractHandler<typeof Spec>: ${m.handlerPath}`)}if(o.checkTests){let e=[m.handlerTestPath,m.componentTestPath,m.formTestPath].filter(e=>typeof e==`string`);for(let t of e)await s.exists(t)||c.push(`Missing test file: ${t}`)}return{valid:c.length===0,errors:c,warnings:l,expected:m}}export{r as validateImplementationFiles};
@@ -0,0 +1 @@
1
+ import{validateSpecStructure as e}from"@lssm/module.contractspec-workspace";async function t(t,n,r={}){let{fs:i}=n;if(!await i.exists(t))return{valid:!1,errors:[`Spec file not found: ${t}`],warnings:[]};let a=await i.readFile(t),o=i.basename(t),s=[],c=[],l;return r.skipStructure||(l=e(a,o),s.push(...l.errors),c.push(...l.warnings)),{valid:s.length===0,structureResult:l,errors:s,warnings:c}}async function n(e,n,r={}){let i=new Map;for(let a of e){let e=await t(a,n,r);i.set(a,e)}return i}export{t as validateSpec,n as validateSpecs};
@@ -0,0 +1 @@
1
+ import{validateSpec as e}from"./validate.js";import{buildSpec as t}from"./build.js";function n(n,r,i,a){let{watcher:o,fs:s,logger:c}=n,l=o.watch(i),u=a?.validate??(async t=>{await e(t,{fs:s,logger:c})}),d=a?.build??(async e=>{await t(e,{fs:s,logger:c},r)});return l.on(async e=>{e.type===`change`&&(c.info(`watchSpecs.changed`,{path:e.path}),i.runValidate&&await u(e.path),i.runBuild&&(i.dryRun?c.info(`[dry-run] watchSpecs skipped build`,{path:e.path}):await d(e.path)))}),l}export{n as watchSpecs};
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@lssm/bundle.contractspec-workspace",
3
+ "version": "0.0.0-canary-20251213172311",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "files": [
9
+ "dist",
10
+ "README.md"
11
+ ],
12
+ "scripts": {
13
+ "publish:pkg": "bun publish --tolerate-republish --ignore-scripts --verbose",
14
+ "build": "bun build:bundle && bun build:types",
15
+ "build:bundle": "tsdown",
16
+ "build:types": "tsc --noEmit",
17
+ "dev": "bun build:bundle --watch",
18
+ "clean": "rimraf dist .turbo",
19
+ "lint": "bun lint:fix",
20
+ "lint:fix": "eslint src --fix",
21
+ "lint:check": "eslint src",
22
+ "test": "bun run"
23
+ },
24
+ "dependencies": {
25
+ "@lssm/module.contractspec-workspace": "workspace:*",
26
+ "@lssm/lib.contracts": "workspace:*",
27
+ "@lssm/lib.schema": "workspace:*",
28
+ "@lssm/lib.testing": "workspace:*",
29
+ "ai": "beta",
30
+ "@ai-sdk/anthropic": "beta",
31
+ "@ai-sdk/openai": "beta",
32
+ "ollama-ai-provider": "^1.2.0",
33
+ "zod": "^4.1.13",
34
+ "glob": "^11.0.1",
35
+ "chokidar": "^4.0.1"
36
+ },
37
+ "devDependencies": {
38
+ "@lssm/tool.tsdown": "workspace:*",
39
+ "@lssm/tool.typescript": "workspace:*",
40
+ "@types/node": "^22.10.2",
41
+ "tsdown": "^0.17.0",
42
+ "typescript": "^5.9.3"
43
+ },
44
+ "exports": {
45
+ ".": "./src/index.ts",
46
+ "./*": "./*"
47
+ },
48
+ "publishConfig": {
49
+ "access": "public",
50
+ "exports": {
51
+ ".": "./dist/index.js",
52
+ "./*": "./*"
53
+ }
54
+ }
55
+ }