stillmade-sdk 0.1.27 → 0.1.29

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/AGENTS.md CHANGED
@@ -4,7 +4,7 @@ This folder is the StillMade Block SDK. Read this file before writing code; it i
4
4
 
5
5
  ## Readiness gate
6
6
 
7
- Do not tell the user a Block is ready, done or working until `node packages/block-cli/cli.js gate .` passes after your last edit, and report the exact status it prints. In this folder Claude Code (`.claude/settings.json`) and Cursor (`.cursor/hooks.json`) run the gate when you finish a turn and send any failures back to you, but only in a session started in this folder: if you downloaded the SDK during the current session, run the gate yourself. Other agents must also run it themselves. If you cannot fix a failure, say plainly that the Block is not ready and list the failing checks.
7
+ Do not tell the user a Block is ready, done or working until `node packages/block-cli/cli.js gate .` passes after your last edit, and report the exact status it prints. In this folder Claude Code (`.claude/settings.json`) and Cursor (`.cursor/hooks.json`) run the gate when you finish a turn and send any failures back to you, but only in a session started in this folder: if you downloaded the SDK during the current session, run the gate yourself. Other agents must also run it themselves. A module Block runs code, so the gate a hook starts does not run it and says so: run `gate` or `ready` on it yourself. If you cannot fix a failure, say plainly that the Block is not ready and list the failing checks.
8
8
 
9
9
  Contract `stillmade.block-builder@1.6.1`. Humans, coding agents, imported source, GitHub repositories and generated code all adapt to the Block platform; anything that becomes a StillMade Block obeys this contract.
10
10
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stillmade-sdk",
3
- "version": "0.1.27",
3
+ "version": "0.1.29",
4
4
  "description": "StillMade Block SDK, CLI and examples",
5
5
  "license": "PolyForm-Shield-1.0.0",
6
6
  "type": "module",
@@ -193,8 +193,11 @@ export async function readPackage(path) {
193
193
  if(localModule||viewFiles)moduleFiles.set(pkg,{...(localModule?.files||{}),...(viewFiles||{})});
194
194
  return pkg;
195
195
  }
196
+ export const MODULE_EXECUTION_REQUIRED_MESSAGE='This Block runs code, so the gate a hook starts does not run it. Run `stillmade-block gate .` (`npx stillmade-block gate .` in an npm project) yourself and report the status it prints.';
196
197
  export async function main(rawArgs) {
197
- const mockRequested=rawArgs.includes('--mock'),args=rawArgs.filter(value=>value!=='--mock');
198
+ // --no-module-execution: what the gate a hook starts passes to ready, so a module Block is refused
199
+ // before anything of it runs; the decision is made on the same parsed package ready would run.
200
+ const mockRequested=rawArgs.includes('--mock'),noModuleExecution=rawArgs.includes('--no-module-execution'),args=rawArgs.filter(value=>value!=='--mock'&&value!=='--no-module-execution');
198
201
  if(!args.length||['help','--help','-h'].includes(args[0])||args.includes('--help')||args.includes('-h'))return {help:HELP};
199
202
  if(args[0]==='templates'||args[0]==='create'&&args.includes('--list'))return {templates:Object.entries(TEMPLATES).map(([name,template])=>({name,runtime:template.runtime,description:template.description}))};
200
203
  const [command, target = '.', extra] = args, path = resolve(target);
@@ -251,7 +254,9 @@ export async function main(rawArgs) {
251
254
  // On the creator's own machine a module Block runs in the SDK's Node executor with the
252
255
  // stand-in host that answers from tests/mocks.json, as test and preview do (October 20,
253
256
  // 2026: ready refused every module Block at DOD-01, so none could pass the gate from the SDK).
254
- const mock=pkg.manifest?.runtime==='module'?await standIns(path,pkg,true):null;
257
+ const module=pkg.manifest?.runtime==='module';
258
+ if(module&&noModuleExecution)throw Object.assign(new Error(MODULE_EXECUTION_REQUIRED_MESSAGE),{code:'MODULE_EXECUTION_REQUIRED'});
259
+ const mock=module?await standIns(path,pkg,true):null;
255
260
  const readiness=await assessBlockReadiness(pkg,{moduleExecution:true,files:moduleFiles.get(pkg),...(mock?{host:mock.host}:{})});
256
261
  if(['FAILED','BLOCKED BY PLATFORM CAPABILITY'].includes(readiness.status))throw Object.assign(new Error(`Block readiness: ${readiness.status}`),{report:readiness});
257
262
  return readiness;
@@ -82,9 +82,15 @@ export async function sdkBaselineForFiles(files){
82
82
  async function readJson(path,fallback){try{return JSON.parse(await readFile(path,'utf8'));}catch{return fallback;}}
83
83
  const summarize=readiness=>(readiness?.items||[]).filter(item=>['failed','blocked'].includes(item.state)).slice(0,6).map(item=>`${item.id} ${item.title}: ${String(item.reason||'').slice(0,300)}`);
84
84
 
85
- async function judge(path,main){
85
+ // A module Block's code runs in a Node worker, which is no isolation boundary, so only a gate the
86
+ // person or agent started themselves runs it (`gate`, `ready`); the gate a hook starts at the end
87
+ // of a turn does not, and says so (October 20, 2026).
88
+ export const RUN_READY_YOURSELF='RUN READY YOURSELF';
89
+ async function judge(path,main,{executeModules=true}={}){
86
90
  try{
87
- const readiness=await main(['ready',path]);
91
+ // ready decides on the package it would run: with --no-module-execution it refuses a module
92
+ // Block before anything of it runs (MODULE_EXECUTION_REQUIRED), so the gate never falls open.
93
+ const readiness=await main(['ready',path,...(executeModules?[]:['--no-module-execution'])]);
88
94
  // Every new or edited Block carries its feature map (builder-contract); search, matching and its listing read it.
89
95
  const manifest=(await main(['inspect',path]).catch(()=>null))?.manifest;
90
96
  // The starting map create writes (one entry copied from the description) does not count until it is rewritten.
@@ -97,6 +103,7 @@ async function judge(path,main){
97
103
  try{await main(['test',path]);return {ok:true,status:'PROJECT TYPE TESTS PASSED',failures:[]};}
98
104
  catch(typeError){return {ok:false,status:'FAILED',failures:[String(typeError.message).slice(0,300)]};}
99
105
  }
106
+ if(error.code==='MODULE_EXECUTION_REQUIRED')return {ok:false,status:RUN_READY_YOURSELF,failures:[String(error.message).slice(0,400)],limitations:0};
100
107
  if(error.code==='APPLICATION_REVIEW_REQUIRED')return {ok:false,status:'NEEDS STILLMADE REVIEW',failures:[String(error.message).slice(0,400)]};
101
108
  if(error.report?.status)return {ok:false,status:error.report.status,failures:summarize(error.report)};
102
109
  return {ok:false,status:'FAILED',failures:[String(error.message).slice(0,300)]};
@@ -104,7 +111,7 @@ async function judge(path,main){
104
111
  }
105
112
 
106
113
  /** Judge every changed package. Never throws for a failing Block; returns a verdict. */
107
- export async function runReadyGate({root=process.cwd(),main}={}){
114
+ export async function runReadyGate({root=process.cwd(),main,executeModules=true}={}){
108
115
  root=resolve(root);
109
116
  main??=(await import('./cli.js')).main;
110
117
  const baseline=(await readJson(join(root,SDK_BASELINE_FILE),{packages:{}})).packages||{};
@@ -116,8 +123,10 @@ export async function runReadyGate({root=process.cwd(),main}={}){
116
123
  const cached=cache.entries[name];
117
124
  // A verdict is reused only for the same package under the same contract: a newer SDK judges again
118
125
  // (October 20, 2026: a FAILED verdict from the SDK that could not run module Blocks outlived the fix).
119
- const verdict=cached?.digest===digest&&cached?.contract===BUILDER_CONTRACT_STAMP?cached:{digest,contract:BUILDER_CONTRACT_STAMP,...await judge(path,main),checkedAt:new Date().toISOString()};
120
- cache.entries[name]=verdict;
126
+ const verdict=cached?.digest===digest&&cached?.contract===BUILDER_CONTRACT_STAMP?cached:{digest,contract:BUILDER_CONTRACT_STAMP,...await judge(path,main,{executeModules}),checkedAt:new Date().toISOString()};
127
+ // A verdict the hook could not reach (a module Block it did not run) is not kept: the person's own
128
+ // gate run judges it, and that verdict then answers the hook too.
129
+ if(verdict.status!==RUN_READY_YOURSELF)cache.entries[name]=verdict;
121
130
  results.push({package:name,...verdict});
122
131
  }
123
132
  await mkdir(join(root,READY_GATE_STATE),{recursive:true});
@@ -134,7 +143,7 @@ export function gateMessage(verdict){
134
143
  /** Hook entry. `agent` is claude, cursor or plain. Returns {exitCode, stdout}. */
135
144
  export async function readyGateHook({agent='plain',input={},root,main}={}){
136
145
  root=resolve(root||(agent==='cursor'?process.env.CURSOR_PROJECT_DIR||input.workspace_roots?.[0]:process.env.CLAUDE_PROJECT_DIR||input.cwd)||process.cwd());
137
- const verdict=await runReadyGate({root,main});
146
+ const verdict=await runReadyGate({root,main,executeModules:false});
138
147
  const statePath=join(root,READY_GATE_STATE,'gate-state.json'),state=await readJson(statePath,{});
139
148
  if(verdict.passed){
140
149
  await writeFile(statePath,'{}\n');
@@ -152,5 +152,5 @@ export function renderBuilderContract(form='compact',{scope='all'}={}){
152
152
  `## Platform gap classes\n\n${Object.entries(GAP_CLASSES).map(([key,label])=>`- ${key}: ${label}`).join('\n')}`,
153
153
  ].join('\n\n');
154
154
  if(form==='full')return `# StillMade Block builder contract\n\n${body}`;
155
- return `# Building StillMade Blocks in this folder\n\nThis folder is the StillMade Block SDK. Read this file before writing code; it is the same contract StillMade's own AI and importers follow. Commands below use the downloaded SDK folder form \`node packages/block-cli/cli.js\`; when the SDK came from \`npm install --save-dev stillmade-sdk\`, type \`npx stillmade-block\` in its place. Scaffold with \`node packages/block-cli/cli.js create my-block javascript\`, then iterate with \`validate my-block\` and \`test my-block\`, check admission with \`pack my-block /tmp/my-block.stillmade-block --original\`, and finish with \`ready my-block\`, the same readiness check StillMade runs before an install or a release. \`SDK.md\` is the complete reference and \`PASTE_TO_LLM.md\` the detailed authoring guide.\n\n## Readiness gate\n\nDo not tell the user a Block is ready, done or working until \`node packages/block-cli/cli.js gate .\` passes after your last edit, and report the exact status it prints. In this folder Claude Code (\`.claude/settings.json\`) and Cursor (\`.cursor/hooks.json\`) run the gate when you finish a turn and send any failures back to you, but only in a session started in this folder: if you downloaded the SDK during the current session, run the gate yourself. Other agents must also run it themselves. If you cannot fix a failure, say plainly that the Block is not ready and list the failing checks.\n\n${body}`;
155
+ return `# Building StillMade Blocks in this folder\n\nThis folder is the StillMade Block SDK. Read this file before writing code; it is the same contract StillMade's own AI and importers follow. Commands below use the downloaded SDK folder form \`node packages/block-cli/cli.js\`; when the SDK came from \`npm install --save-dev stillmade-sdk\`, type \`npx stillmade-block\` in its place. Scaffold with \`node packages/block-cli/cli.js create my-block javascript\`, then iterate with \`validate my-block\` and \`test my-block\`, check admission with \`pack my-block /tmp/my-block.stillmade-block --original\`, and finish with \`ready my-block\`, the same readiness check StillMade runs before an install or a release. \`SDK.md\` is the complete reference and \`PASTE_TO_LLM.md\` the detailed authoring guide.\n\n## Readiness gate\n\nDo not tell the user a Block is ready, done or working until \`node packages/block-cli/cli.js gate .\` passes after your last edit, and report the exact status it prints. In this folder Claude Code (\`.claude/settings.json\`) and Cursor (\`.cursor/hooks.json\`) run the gate when you finish a turn and send any failures back to you, but only in a session started in this folder: if you downloaded the SDK during the current session, run the gate yourself. Other agents must also run it themselves. A module Block runs code, so the gate a hook starts does not run it and says so: run \`gate\` or \`ready\` on it yourself. If you cannot fix a failure, say plainly that the Block is not ready and list the failing checks.\n\n${body}`;
156
156
  }