diff --git a/README.md b/README.md index c630a56..33fe838 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,30 @@ **Claro** is a small, readable scripting language for beginners. It is designed to make programming approachable without requiring punctuation-heavy syntax, while still providing a path toward functions, types, objects, files, packages, and networking. -Current release: **v1.18.26** +Current release: **v2026.10.20.155641** + +## Versioning + +Development/release runs use their current UTC date and time: `vYYYY.MM.DD.HHMMSS`. +Stamp once at the beginning of each run, then build: + +```bash +python3 tools/stamp_version.py +make +``` + +Ordinary rebuilds retain the run version. The stamp updates current runtime, +IDE metadata, generated project/lockfile versions, validators, and current docs; +historical release records remain unchanged. Existing lockfiles from a previous +run can be refreshed with `claro package lock`. ## Quick start +Claro's learning-first development charter and current priorities are in +[`docs/LEARNING_FIRST.md`](docs/LEARNING_FIRST.md). It supersedes older +feature-expansion priorities for current development; historical roadmaps remain +available as project history. + Build Claro with a C99 compiler and `make`: ```bash diff --git a/docs/CLASSROOM_READINESS.md b/docs/CLASSROOM_READINESS.md index 01b0f77..3c9f4c7 100644 --- a/docs/CLASSROOM_READINESS.md +++ b/docs/CLASSROOM_READINESS.md @@ -1,6 +1,6 @@ # Classroom Readiness Checklist -Claro v1.18.26 is suitable for beginner classroom-style testing of the stable console-language foundation. Advanced platform features should still be introduced carefully and only after the basics are comfortable. +Claro v2026.10.20.155641 is suitable for beginner classroom-style testing of the stable console-language foundation. Advanced platform features should still be introduced carefully and only after the basics are comfortable. ## Ready to test @@ -25,7 +25,7 @@ Claro v1.18.26 is suitable for beginner classroom-style testing of the stable co ## Recommended testing session 1. Build Claro or use the included executable. -2. Run `claro --version` and confirm `Claro v1.18.26`. +2. Run `claro --version` and confirm `Claro v2026.10.20.155641`. 3. Run `claro test`. 4. Run `claro validate`. 5. Run `lessons/01_hello.claro`. diff --git a/docs/CLI.md b/docs/CLI.md index f149245..e4b4e73 100644 --- a/docs/CLI.md +++ b/docs/CLI.md @@ -1,6 +1,6 @@ # Claro Command Line -Claro v1.18.26 supports these main commands: +Claro v2026.10.20.155641 supports these main commands: ```bash claro file.claro @@ -58,7 +58,7 @@ claro package doctor claro package lock ``` -The v1.18.26 package command is a safer local project-file helper. It creates `claro.project`, `claro.lock`, and local folders under `packages/`. Package names may appear only once in `claro.project`; `claro package doctor` reports duplicate entries so the lockfile stays unambiguous. It also requires the project manifest to have exactly one non-empty `name:` field. It is not yet an online package registry. +The v2026.10.20.155641 package command is a safer local project-file helper. It creates `claro.project`, `claro.lock`, and local folders under `packages/`. Package names may appear only once in `claro.project`; `claro package doctor` reports duplicate entries so the lockfile stays unambiguous. It also requires the project manifest to have exactly one non-empty `name:` field. It is not yet an online package registry. ## IDE metadata diff --git a/docs/COMPLETE_PLATFORM_ROADMAP.md b/docs/COMPLETE_PLATFORM_ROADMAP.md index 6b486aa..7f3c6f6 100644 --- a/docs/COMPLETE_PLATFORM_ROADMAP.md +++ b/docs/COMPLETE_PLATFORM_ROADMAP.md @@ -1,6 +1,6 @@ # Claro Complete Platform Roadmap -Current release: **Claro v1.18.26** +Current release: **Claro v2026.10.20.155641** This roadmap tracks the larger goal: Claro as a complete beginner-friendly programming platform, not only a toy scripting interpreter. diff --git a/docs/CURRENT_STATUS.md b/docs/CURRENT_STATUS.md index 09ae9c6..40c34b9 100644 --- a/docs/CURRENT_STATUS.md +++ b/docs/CURRENT_STATUS.md @@ -1,4 +1,4 @@ -# Claro v1.18.26 Current Status +# Claro v2026.10.20.155641 Current Status This file is the beginner-safe status map for the current package. It separates what is ready to use from what is historical, experimental, or still planned. @@ -11,7 +11,7 @@ This file is the beginner-safe status map for the current package. It separates ## Security and correctness review progress -The v1.18.26 review identified missing-argument reads in standard-library built-ins. The current runtime now validates required argument counts centrally before any builtin indexes `args[]`. Covered calls include `math.abs`, `math.clamp`, `random.seed`, `random.int`, text helpers, CSV helpers, path helpers, and collection helpers. Missing arguments produce a beginner-facing `needs N arguments` runtime error. +The v2026.10.20.155641 review identified missing-argument reads in standard-library built-ins. The current runtime now validates required argument counts centrally before any builtin indexes `args[]`. Covered calls include `math.abs`, `math.clamp`, `random.seed`, `random.int`, text helpers, CSV helpers, path helpers, and collection helpers. Missing arguments produce a beginner-facing `needs N arguments` runtime error. Focused regression coverage: `tests/38_builtin_arity.claro`, `tests/39_random_inverted_range.claro`, and `tests/40_call_depth_guard.claro`. @@ -197,7 +197,7 @@ Ready now: - `claro package doctor` compares the complete manifest `checksum:` value, so a valid checksum followed by extra text is rejected - `claro package doctor` requires the complete supported `manifest-version: 1` value in `claro.project`, so a prefix such as `manifest-version: 10` is rejected - `claro package doctor` requires the complete supported `version: 1` value in each local package manifest, so a prefix such as `version: 10` is rejected -- `claro package doctor` requires exactly one current `version: v1.18.26` line in `claro.lock`, so stale, missing, blank, or duplicate lockfile release versions are rejected +- `claro package doctor` requires exactly one current `version: v2026.10.20.155641` line in `claro.lock`, so stale, missing, blank, or duplicate lockfile release versions are rejected - `claro package doctor` requires the complete supported `source: local` value in each local package manifest, so a prefix such as `source: local-extra` is rejected - package project entries must be unique; `claro package doctor`, `list`, `add`, `init`, and `lock` reject duplicate names instead of generating ambiguous package or lockfile data - lockfile package entries must also be unique; `claro package doctor` reports duplicate lock entries instead of accepting ambiguous package/checksum data @@ -301,7 +301,7 @@ Files named `RC*_NOTES.md`, `RC*_VALIDATION.md`, older `V1_*_VALIDATION.md`, and ## Which docs should a new learner trust first? -For the current v1.18.26 package, read docs in this order: +For the current v2026.10.20.155641 package, read docs in this order: 1. `QUICK_START.md`, `FIRST_HOUR.md`, and `lessons/README.md` for first programs. 2. `README.md` and this file for the current feature map. diff --git a/docs/FINAL_RELEASE_CHECKLIST.md b/docs/FINAL_RELEASE_CHECKLIST.md index ed9d5b6..0bd6556 100644 --- a/docs/FINAL_RELEASE_CHECKLIST.md +++ b/docs/FINAL_RELEASE_CHECKLIST.md @@ -1,6 +1,6 @@ # Claro Final Release Checklist -Before publishing `Claro v1.18.26` as the current stable release: +Before publishing `Claro v2026.10.20.155641` as the current stable release: - Build from a clean unzip on Windows. - Build from a clean unzip on Linux or macOS. diff --git a/docs/GRAPHICS.md b/docs/GRAPHICS.md index 90b5581..1ad2ada 100644 --- a/docs/GRAPHICS.md +++ b/docs/GRAPHICS.md @@ -1,6 +1,6 @@ # Claro Graphics Status -Current release: **Claro v1.18.26** +Current release: **Claro v2026.10.20.155641** Graphics support is intentionally separated from the stable beginner console workflow. diff --git a/docs/LEARNING_FIRST.md b/docs/LEARNING_FIRST.md new file mode 100644 index 0000000..b0be6d1 --- /dev/null +++ b/docs/LEARNING_FIRST.md @@ -0,0 +1,16 @@ +# Claro Learning-First Development Charter + +Claro is a learning tool first and a general-purpose programming language second. Prioritize an easy first experience, especially for learners with learning disabilities, without assuming one design suits every learner or claiming accessibility validation without learner testing. + +## Active priorities + +- Keep syntax, rules, and behavior simple, predictable, and consistent. +- Favor the small core: `SAY`/`ASK`, variables, basic arithmetic, decisions, loops, lists, simple functions, and text-file save/load. +- Teach one recommended syntax per concept. Keep supported compatibility forms working, but outside the initial learning path. +- Teach one concept at a time with short explanations, runnable examples, small exercises, visible results, and concrete expected output. +- Make errors identify the line, explain the problem without blame, and show a repair. +- Fix broken examples, inconsistent core behavior, and simple-core crashes before considering features. +- Do not pursue advanced type systems, concurrency, package registries, web frameworks, complex object models, or graphics systems absent an explicit request. +- Claro runs trusted scripts; it is not a sandbox. + +Older roadmap documents preserve project history and describe prior ambitions. This charter guides current work without removing supported features or erasing that history. diff --git a/docs/PERFORMANCE.md b/docs/PERFORMANCE.md index 83a3f85..997888e 100644 --- a/docs/PERFORMANCE.md +++ b/docs/PERFORMANCE.md @@ -1,6 +1,6 @@ # Claro Performance and Optimization Notes -Current release: **Claro v1.18.26** +Current release: **Claro v2026.10.20.155641** Performance work should come after language behavior is stable and measured. Do not optimize blindly. diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 09894f9..b53e5fc 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -1,5 +1,10 @@ # Claro Roadmap +> **Current direction:** Follow [`LEARNING_FIRST.md`](LEARNING_FIRST.md) for +> active priorities. The broader milestones below are historical planning +> context, not a mandate to expand the language or a statement that features +> have been removed. + Current release: **Claro v1.18.26** Claro is now in the stable v1 development line. Older RC milestones are kept as historical notes in files such as `RC0_NOTES.md`, `RC3_NOTES.md`, and `RC9_VALIDATION.md`; they are not the current roadmap. diff --git a/docs/SDL12.md b/docs/SDL12.md index 5166586..ea40580 100644 --- a/docs/SDL12.md +++ b/docs/SDL12.md @@ -2,7 +2,7 @@ SDL 1.2 support is **not included in the current stable executable**. -The `examples/experimental_sdl/` files are design experiments / future examples only. In Claro v1.18.26, running them with the included `claro.exe` will fail with: +The `examples/experimental_sdl/` files are design experiments / future examples only. In Claro v2026.10.20.155641, running them with the included `claro.exe` will fail with: ```text Unknown command: SDL diff --git a/docs/SPEC.md b/docs/SPEC.md index 32bcfb6..7885d66 100644 --- a/docs/SPEC.md +++ b/docs/SPEC.md @@ -1,6 +1,6 @@ # Claro Language Specification -Current package: **Claro v1.18.26** +Current package: **Claro v2026.10.20.155641** Status: **Living v1 specification** This document defines the intended behavior of the Claro language. It is written to reduce breaking changes and keep the v1 line teachable and predictable. diff --git a/docs/V1_16_PACKAGES_PROJECTS.md b/docs/V1_16_PACKAGES_PROJECTS.md index 70b0f7a..ab5c5d5 100644 --- a/docs/V1_16_PACKAGES_PROJECTS.md +++ b/docs/V1_16_PACKAGES_PROJECTS.md @@ -1,6 +1,6 @@ -# Claro v1.18.26 - Packages and Projects +# Claro v2026.10.20.155641 - Packages and Projects -Claro v1.18.26 makes projects and packages safer and more useful while keeping the beginner syntax simple. +Claro v2026.10.20.155641 makes projects and packages safer and more useful while keeping the beginner syntax simple. ## Create a project @@ -28,7 +28,7 @@ README.md manifest-version: 1 name: MyProject main: main.claro -version: v1.18.26 +version: v2026.10.20.155641 packages: package: text package: net_tools @@ -77,10 +77,10 @@ If `package doctor` reports `BAD lock checksum: math-tools`, check the `math-too BAD lock version: expected exactly one lock-version: 1 line ``` -The lockfile must also contain exactly one current release line, `version: v1.18.26`. A missing, stale, blank, or duplicate release line fails with: +The lockfile must also contain exactly one current release line, `version: v2026.10.20.155641`. A missing, stale, blank, or duplicate release line fails with: ```text -BAD lock release version: expected exactly one version: v1.18.26 line +BAD lock release version: expected exactly one version: v2026.10.20.155641 line ``` The doctor leaves your files unchanged. After reviewing your project and package manifests, run `claro package lock` to regenerate the lockfile, then run `claro package doctor` again. Spaces around values and capitalization of the lock-format field name are accepted, as with other lockfile fields. Valid generated lockfiles continue to work unchanged; this check does not add registry downloads or verify package content hashes. diff --git a/docs/WEB_SERVER_PLAN.md b/docs/WEB_SERVER_PLAN.md index 782b9f6..6cb55af 100644 --- a/docs/WEB_SERVER_PLAN.md +++ b/docs/WEB_SERVER_PLAN.md @@ -1,6 +1,6 @@ # Claro Web Server Plan -Current release: **Claro v1.18.26** +Current release: **Claro v2026.10.20.155641** Claro currently has beginner HTTP client commands and offline `claro://` test URLs. A web server API is not complete yet. diff --git a/src/claro.c b/src/claro.c index f1da1b5..8972cb6 100644 --- a/src/claro.c +++ b/src/claro.c @@ -1,5 +1,5 @@ /* - Claro v1.18.26 - Networking Reliability Release + Claro v2026.10.20.155641 - Networking Reliability Release A compact, buildable Claro interpreter focused on the beginner-friendly teaching core: readable commands, simple expressions, functions, collections, @@ -43,7 +43,7 @@ int pclose(FILE *stream); #define F_OK 0 #endif -#define CLARO_VERSION "Claro v1.18.26" +#define CLARO_VERSION "Claro v2026.10.20.155641" #define CLARO_MAX_CALL_DEPTH 256 static void *xmalloc(size_t n){ void *p=malloc(n?n:1); if(!p){ fprintf(stderr,"Out of memory\n"); exit(1);} return p; } @@ -728,7 +728,7 @@ static int run_tests(void){ int fails=0; static int write_text_file_simple(const char *path,const char *text){ FILE *f=fopen(path,"wb"); if(!f) return 0; fputs(text,f); fclose(f); return 1; } static int project_name_safe(const char *name){ int i; if(!name||!*name) return 0; if(strlen(name)>64) return 0; if(name[0]=='.'||name[0]=='-'||name[0]=='_') return 0; for(i=0;name[i];i++){ unsigned char c=(unsigned char)name[i]; if(!(isalnum(c)||c=='_'||c=='-')) return 0; } return 1; } -static int create_new_project(const char *name){ char path[512]; char text[1024]; if(!name||!*name){ fprintf(stderr,"Project needs a name. Try: claro new MyProject\n"); return 1; } if(strlen(name)>64){ fprintf(stderr,"Project names must be 64 characters or fewer.\n"); return 1; } if(!project_name_safe(name)){ fprintf(stderr,"Project names may use only letters, numbers, dash, and underscore.\n"); return 1; } if(!make_folder(name)){ fprintf(stderr,"Could not create project folder: %s\n",name); return 1; } snprintf(path,sizeof(path),"%s/main.claro",name); write_text_file_simple(path,"SAY \"Welcome to Claro!\"\n\nSET name TO \"Learner\"\nSAY \"Hello \" + name\n\nTEACH greet person\n SAY \"Nice to meet you, \" + person\nEND\n\nDO greet \"Friend\"\n"); snprintf(path,sizeof(path),"%s/packages",name); make_folder(path); snprintf(path,sizeof(path),"%s/claro.project",name); snprintf(text,sizeof(text),"manifest-version: 1\nname: %s\nmain: main.claro\nversion: v1.18.26\npackages:\n",name); write_text_file_simple(path,text); snprintf(path,sizeof(path),"%s/claro.lock",name); write_text_file_simple(path,"# Claro package lock\nlock-version: 1\nversion: v1.18.26\n"); snprintf(path,sizeof(path),"%s/README.md",name); write_text_file_simple(path,"# My Claro Project\n\nBuild Claro, then run this project with:\n\n```bash\nclaro run\n```\n\nUse packages with:\n\n```bash\nclaro package add text\nclaro package list\nclaro package doctor\n```\n"); printf("Created Claro project: %s\n",name); printf("Next steps:\n cd %s\n claro run\n",name); return 0; } +static int create_new_project(const char *name){ char path[512]; char text[1024]; if(!name||!*name){ fprintf(stderr,"Project needs a name. Try: claro new MyProject\n"); return 1; } if(strlen(name)>64){ fprintf(stderr,"Project names must be 64 characters or fewer.\n"); return 1; } if(!project_name_safe(name)){ fprintf(stderr,"Project names may use only letters, numbers, dash, and underscore.\n"); return 1; } if(!make_folder(name)){ fprintf(stderr,"Could not create project folder: %s\n",name); return 1; } snprintf(path,sizeof(path),"%s/main.claro",name); write_text_file_simple(path,"SAY \"Welcome to Claro!\"\n\nSET name TO \"Learner\"\nSAY \"Hello \" + name\n\nTEACH greet person\n SAY \"Nice to meet you, \" + person\nEND\n\nDO greet \"Friend\"\n"); snprintf(path,sizeof(path),"%s/packages",name); make_folder(path); snprintf(path,sizeof(path),"%s/claro.project",name); snprintf(text,sizeof(text),"manifest-version: 1\nname: %s\nmain: main.claro\nversion: v2026.10.20.155641\npackages:\n",name); write_text_file_simple(path,text); snprintf(path,sizeof(path),"%s/claro.lock",name); write_text_file_simple(path,"# Claro package lock\nlock-version: 1\nversion: v2026.10.20.155641\n"); snprintf(path,sizeof(path),"%s/README.md",name); write_text_file_simple(path,"# My Claro Project\n\nBuild Claro, then run this project with:\n\n```bash\nclaro run\n```\n\nUse packages with:\n\n```bash\nclaro package add text\nclaro package list\nclaro package doctor\n```\n"); printf("Created Claro project: %s\n",name); printf("Next steps:\n cd %s\n claro run\n",name); return 0; } static int run_repl(void){ Runtime rt; char line[4096]; Program p; rt_init(&rt); memset(&p,0,sizeof(p)); p.path=xstrdup(""); p.count=1; p.cap=1; p.lines=(char**)xmalloc(sizeof(char*)); printf("%s\n",CLARO_VERSION); printf("Type HELP for help, or EXIT to leave.\n"); while(1){ int pc=0; printf("> "); fflush(stdout); if(!fgets(line,sizeof(line),stdin)) break; { size_t n=strlen(line); while(n&&(line[n-1]=='\n'||line[n-1]=='\r')) line[--n]=0; } { char *t=trim_inplace(line); if(ci_eq(t,"EXIT")||ci_eq(t,"QUIT")) break; if(ci_eq(t,"HELP")){ printf("Try: SAY \"Hello\"\n"); printf("Try: SET name TO \"Jon\"\n"); printf("Try: SAY name\n"); continue; } if(!*t) continue; p.lines[0]=t; exec_line(&rt,&p,&pc,t); if(rt.error){ fprintf(stderr,"%s:%d: %s\n",rt.err_file?rt.err_file:"",rt.err_line,rt.err_msg?rt.err_msg:"error"); rt_clear_error(&rt); } } } return 0; } static int file_exists_simple(const char *path); @@ -736,11 +736,11 @@ static int path_exists_simple(const char *path){ struct stat st; return path&&*p static int package_name_too_long(const char *name){ return name&&strlen(name)>64; } static int package_name_safe(const char *name){ int i; if(!name||!*name) return 0; if(package_name_too_long(name)) return 0; if(name[0]=='.'||name[0]=='-'||name[0]=='_') return 0; for(i=0;name[i];i++){ unsigned char c=(unsigned char)name[i]; if(!(isalnum(c)||c=='_'||c=='-')) return 0; } return 1; } static void package_checksum(const char *name,char *out,size_t n){ unsigned long h=5381; const char *p=name?name:""; while(*p){ h=((h<<5)+h)+(unsigned char)(*p++); } p="|local|1|claro"; while(*p){ h=((h<<5)+h)+(unsigned char)(*p++); } snprintf(out,n,"%08lx",h); } -static void ensure_project_file(void){ if(!file_exists_simple("claro.project")) write_text_file_simple("claro.project","manifest-version: 1\nname: ClaroProject\nmain: main.claro\nversion: v1.18.26\npackages:\n"); } +static void ensure_project_file(void){ if(!file_exists_simple("claro.project")) write_text_file_simple("claro.project","manifest-version: 1\nname: ClaroProject\nmain: main.claro\nversion: v2026.10.20.155641\npackages:\n"); } static char *project_value(const char *key){ char *txt=read_file_text("claro.project"); size_t kn=strlen(key); char *p,*out=NULL; if(!txt) return NULL; p=txt; while(*p){ char *line=p; char save; while(*p&&*p!='\n'&&*p!='\r') p++; save=*p; *p=0; { char *t=trim_inplace(line); if(strnicmp2(t,key,kn)==0 && t[kn]==':'){ out=xstrdup(trim_inplace(t+kn+1)); break; } } if(save) p++; while(*p=='\n'||*p=='\r') p++; } free(txt); return out; } static int package_has_name(const char *name){ char *txt=read_file_text("claro.project"); char *p; int found=0; if(!txt) return 0; p=txt; while(*p){ char *line=p; char save; while(*p&&*p!='\n'&&*p!='\r') p++; save=*p; *p=0; { char *t=trim_inplace(line); if(strnicmp2(t,"package:",8)==0){ char *pkg=trim_inplace(t+8); if(strcmp(pkg,name)==0){ found=1; break; } } } if(save) p++; while(*p=='\n'||*p=='\r') p++; } free(txt); return found; } static int project_package_names_safe(void){ char *txt=read_file_text("claro.project"); char *p; char seen[128][65]; int seen_count=0; if(!txt) return 1; p=txt; while(*p){ char *line=p; char save; while(*p&&*p!='\n'&&*p!='\r') p++; save=*p; *p=0; { char *t=trim_inplace(line); if(strnicmp2(t,"package:",8)==0){ char *pkg=trim_inplace(t+8); int i; if(*pkg&&!package_name_safe(pkg)){ printf("BAD package name: %s\n",pkg); free(txt); return 0; } if(*pkg){ for(i=0;ivalue&&isspace((unsigned char)end[-1])) end--; - if((size_t)(end-value)==8&&strncmp(value,"v1.18.26",8)==0) matches++; + if((size_t)(end-value)==strlen(CLARO_VERSION + 6)&&strncmp(value,CLARO_VERSION + 6,strlen(CLARO_VERSION + 6))==0) matches++; } while(*p=='\n'||*p=='\r') p++; } return fields==1&&matches==1; } -static int package_doctor(void){ int ok=1; char *mainfile; char *txt; printf("%s\n",CLARO_VERSION); printf("Package/project doctor:\n"); printf(" %s claro.project\n",file_exists_simple("claro.project")?"OK":"MISSING"); if(!file_exists_simple("claro.project")) ok=0; mainfile=project_value("main"); if(mainfile&&*mainfile){ printf(" %s main script: %s\n",file_exists_simple(mainfile)?"OK":"MISSING",mainfile); if(!file_exists_simple(mainfile)) ok=0; } else { printf(" MISSING main setting\n"); ok=0; } printf(" %s packages folder\n",path_exists_simple("packages")?"OK":"MISSING"); if(!path_exists_simple("packages")) ok=0; printf(" %s claro.lock\n",file_exists_simple("claro.lock")?"OK":"MISSING"); if(!file_exists_simple("claro.lock")) ok=0; txt=read_file_text("claro.lock"); if(txt&&!package_lock_version_matches(txt)){ printf(" BAD lock version: expected exactly one lock-version: 1 line\n"); ok=0; } if(txt&&!package_lock_release_version_matches(txt)){ printf(" BAD lock release version: expected exactly one version: v1.18.26 line\n"); ok=0; } free(txt); txt=read_file_text("claro.project"); if(txt&&!project_package_names_safe()){ ok=0; } if(txt&& !package_manifest_version_matches(txt)){ printf(" BAD project manifest version: expected 1\n"); ok=0; } if(txt&& !package_project_name_valid(txt)){ printf(" BAD project manifest name: expected a non-empty name\n"); ok=0; } if(txt){ char *p=txt; while(*p){ char *line=p; char save; while(*p&&*p!='\n'&&*p!='\r') p++; save=*p; *p=0; { char *t=trim_inplace(line); if(strnicmp2(t,"package:",8)==0){ char *pkg=trim_inplace(t+8); if(*pkg){ char meta[512], *mt, sum[32]; if(!package_name_safe(pkg)){ printf(" BAD package name: %s\n",pkg); ok=0; } else { snprintf(meta,sizeof(meta),"packages/%s/claro.package",pkg); mt=read_file_text(meta); if(mt&&!package_manifest_version_value_matches(mt)){ printf(" BAD package version: %s\n",pkg); ok=0; } else if(mt&&!package_manifest_source_matches(mt)){ printf(" BAD package source: %s\n",pkg); ok=0; } else if(mt&&!package_manifest_version_matches(mt)){ printf(" BAD package manifest version: %s. Keep exactly one manifest-version: 1 line in packages/%s/claro.package.\n",pkg,pkg); ok=0; } else if(mt&&strstr(mt,"checksum:")){ if(!package_manifest_name_matches(mt,pkg)){ printf(" BAD package manifest name: %s\n",pkg); ok=0; } else { printf(" OK package manifest: %s\n",pkg); package_checksum(pkg,sum,sizeof(sum)); if(package_manifest_checksum_matches(mt,sum)) printf(" OK package checksum: %s\n",pkg); else { printf(" BAD package checksum: %s\n",pkg); ok=0; } if(package_lock_checksum_ok(pkg)) printf(" OK lock checksum: %s\n",pkg); else { printf(" BAD lock checksum: %s\n",pkg); ok=0; } } } else { printf(" MISSING package manifest: %s\n",pkg); ok=0; } free(mt); } } } } if(save) p++; while(*p=='\n'||*p=='\r') p++; } free(txt); } if(!lock_packages_match_project()) ok=0; free(mainfile); printf("%s\n",ok?"Package/project files look ready.":"Package/project files need attention."); return ok?0:1; } +static int package_doctor(void){ int ok=1; char *mainfile; char *txt; printf("%s\n",CLARO_VERSION); printf("Package/project doctor:\n"); printf(" %s claro.project\n",file_exists_simple("claro.project")?"OK":"MISSING"); if(!file_exists_simple("claro.project")) ok=0; mainfile=project_value("main"); if(mainfile&&*mainfile){ printf(" %s main script: %s\n",file_exists_simple(mainfile)?"OK":"MISSING",mainfile); if(!file_exists_simple(mainfile)) ok=0; } else { printf(" MISSING main setting\n"); ok=0; } printf(" %s packages folder\n",path_exists_simple("packages")?"OK":"MISSING"); if(!path_exists_simple("packages")) ok=0; printf(" %s claro.lock\n",file_exists_simple("claro.lock")?"OK":"MISSING"); if(!file_exists_simple("claro.lock")) ok=0; txt=read_file_text("claro.lock"); if(txt&&!package_lock_version_matches(txt)){ printf(" BAD lock version: expected exactly one lock-version: 1 line\n"); ok=0; } if(txt&&!package_lock_release_version_matches(txt)){ printf(" BAD lock release version: expected exactly one version: v2026.10.20.155641 line\n"); ok=0; } free(txt); txt=read_file_text("claro.project"); if(txt&&!project_package_names_safe()){ ok=0; } if(txt&& !package_manifest_version_matches(txt)){ printf(" BAD project manifest version: expected 1\n"); ok=0; } if(txt&& !package_project_name_valid(txt)){ printf(" BAD project manifest name: expected a non-empty name\n"); ok=0; } if(txt){ char *p=txt; while(*p){ char *line=p; char save; while(*p&&*p!='\n'&&*p!='\r') p++; save=*p; *p=0; { char *t=trim_inplace(line); if(strnicmp2(t,"package:",8)==0){ char *pkg=trim_inplace(t+8); if(*pkg){ char meta[512], *mt, sum[32]; if(!package_name_safe(pkg)){ printf(" BAD package name: %s\n",pkg); ok=0; } else { snprintf(meta,sizeof(meta),"packages/%s/claro.package",pkg); mt=read_file_text(meta); if(mt&&!package_manifest_version_value_matches(mt)){ printf(" BAD package version: %s\n",pkg); ok=0; } else if(mt&&!package_manifest_source_matches(mt)){ printf(" BAD package source: %s\n",pkg); ok=0; } else if(mt&&!package_manifest_version_matches(mt)){ printf(" BAD package manifest version: %s. Keep exactly one manifest-version: 1 line in packages/%s/claro.package.\n",pkg,pkg); ok=0; } else if(mt&&strstr(mt,"checksum:")){ if(!package_manifest_name_matches(mt,pkg)){ printf(" BAD package manifest name: %s\n",pkg); ok=0; } else { printf(" OK package manifest: %s\n",pkg); package_checksum(pkg,sum,sizeof(sum)); if(package_manifest_checksum_matches(mt,sum)) printf(" OK package checksum: %s\n",pkg); else { printf(" BAD package checksum: %s\n",pkg); ok=0; } if(package_lock_checksum_ok(pkg)) printf(" OK lock checksum: %s\n",pkg); else { printf(" BAD lock checksum: %s\n",pkg); ok=0; } } } else { printf(" MISSING package manifest: %s\n",pkg); ok=0; } free(mt); } } } } if(save) p++; while(*p=='\n'||*p=='\r') p++; } free(txt); } if(!lock_packages_match_project()) ok=0; free(mainfile); printf("%s\n",ok?"Package/project files look ready.":"Package/project files need attention."); return ok?0:1; } static int run_package_cmd(int argc,char **argv,int arg){ if(arg>=argc){ printf("Package commands:\n claro package init\n claro package add NAME\n claro package remove NAME\n claro package list\n claro package doctor\n claro package lock\n"); return 0; } if(strcmp(argv[arg],"init")==0){ ensure_project_file(); if(!project_package_names_safe()) return 1; make_folder("packages"); if(!write_package_lock()) return 1; printf("Package project ready: claro.project, claro.lock, packages/\n"); return 0; } if(strcmp(argv[arg],"add")==0&&arg+1=argc) return 0; if(strcmp(argv[arg],"help")==0||strcmp(argv[arg],"--help")==0){ print_help(); return 0; } if(strcmp(argv[arg],"test")==0) return run_tests(); if(strcmp(argv[arg],"repl")==0) return run_repl(); if(strcmp(argv[arg],"examples")==0) return show_examples(); if(strcmp(argv[arg],"doctor")==0) return run_doctor(); if(strcmp(argv[arg],"validate")==0) return run_validate(); if(strcmp(argv[arg],"package")==0) return run_package_cmd(argc,argv,arg+1); if(strcmp(argv[arg],"ide")==0) return print_ide_info(); if(strcmp(argv[arg],"new")==0&&arg+1=argc){ Runtime rt; int rc; char *mainfile=project_value("main"); if(!mainfile||!*mainfile){ if(mainfile) free(mainfile); mainfile=xstrdup("main.claro"); } rt_init(&rt); rt.trace=trace; rt.script_argc=0; rt.script_argv=NULL; rc=run_file(&rt,mainfile); free(mainfile); runtime_free(&rt); return rc; } } { Runtime rt; int rc; rt_init(&rt); rt.trace=trace; rt.script_argc=argc-arg-1; rt.script_argv=argv+arg+1; rc=run_file(&rt,argv[arg]); runtime_free(&rt); return rc; } } diff --git a/tools/stamp_version.py b/tools/stamp_version.py new file mode 100644 index 0000000..a78be8d --- /dev/null +++ b/tools/stamp_version.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 +"""Stamp a development/release run once, using its current UTC date and time. + +Run before building: python3 tools/stamp_version.py && make +Ordinary rebuilds and validation retain the stamp for reproducibility. +Historical release notes and changelog entries are deliberately not rewritten. +""" +from datetime import datetime, timezone +from pathlib import Path +import re + +ROOT = Path(__file__).resolve().parents[1] +CURRENT_DOCS = ( + "CURRENT_STATUS.md", "CLASSROOM_READINESS.md", "CLI.md", "SPEC.md", + "FINAL_RELEASE_CHECKLIST.md", "WEB_SERVER_PLAN.md", "GRAPHICS.md", + "PERFORMANCE.md", "COMPLETE_PLATFORM_ROADMAP.md", "SDL12.md", + "V1_16_PACKAGES_PROJECTS.md", +) + + +def main(): + source = ROOT / "src/claro.c" + match = re.search(r'#define CLARO_VERSION "Claro ([^"\n]+)"', source.read_text()) + if not match: + raise SystemExit("Cannot find CLARO_VERSION; no files changed") + previous = match.group(1) + version = datetime.now(timezone.utc).strftime("v%Y.%m.%d.%H%M%S") + paths = [source, ROOT / "README.md"] + paths += sorted((ROOT / "tools").glob("*.py")) + paths += [ROOT / "docs" / name for name in CURRENT_DOCS] + updates = [] + for path in paths: + if path == Path(__file__).resolve() or not path.exists(): + continue + old = path.read_text(encoding="utf-8") + new = old.replace(previous, version) + if new != old: + updates.append((path, new)) + for path, content in updates: + path.write_text(content, encoding="utf-8") + print(f"Claro {version} (UTC); updated {len(updates)} current source/docs/validator files") + + +if __name__ == "__main__": + main() diff --git a/tools/validate_package_security.py b/tools/validate_package_security.py index 7a19cf6..67244f8 100644 --- a/tools/validate_package_security.py +++ b/tools/validate_package_security.py @@ -6,7 +6,7 @@ from pathlib import Path ROOT = Path(__file__).resolve().parents[1] EXE = ROOT / ("claro.exe" if os.name == "nt" else "claro") -EXPECTED_VERSION = "Claro v1.18.26" +EXPECTED_VERSION = "Claro v2026.10.20.155641" def fail(message): @@ -57,11 +57,11 @@ def main(): fail(out) starter = work / "StarterApp" starter_project = read(starter / "claro.project") - for phrase in ["manifest-version: 1", "name: StarterApp", "main: main.claro", "version: v1.18.26", "packages:"]: + for phrase in ["manifest-version: 1", "name: StarterApp", "main: main.claro", "version: v2026.10.20.155641", "packages:"]: if phrase not in starter_project: fail(f"claro new project file missing {phrase!r}:\n{starter_project}") starter_lock = read(starter / "claro.lock") - for phrase in ["lock-version: 1", "version: v1.18.26"]: + for phrase in ["lock-version: 1", "version: v2026.10.20.155641"]: if phrase not in starter_lock: fail(f"claro new lockfile missing {phrase!r}:\n{starter_lock}") @@ -71,7 +71,7 @@ def main(): if rc != 0: fail(out) project = read(work / "claro.project") - for phrase in ["manifest-version: 1", "name: ClaroProject", "main: main.claro", "version: v1.18.26", "packages:"]: + for phrase in ["manifest-version: 1", "name: ClaroProject", "main: main.claro", "version: v2026.10.20.155641", "packages:"]: if phrase not in project: fail(f"claro.project missing {phrase!r}:\n{project}") @@ -100,7 +100,7 @@ def main(): fail(f"Package checksum should be a stable non-placeholder value, got {checksum!r}") lock = read(work / "claro.lock") - for phrase in ["lock-version: 1", "version: v1.18.26", "package: math-tools", "checksum:"]: + for phrase in ["lock-version: 1", "version: v2026.10.20.155641", "package: math-tools", "checksum:"]: if phrase not in lock: fail(f"claro.lock missing {phrase!r}:\n{lock}") @@ -127,12 +127,12 @@ def main(): fail(f"Lock format-version diagnostic was unclear:\n{out}") if read(work / "claro.lock") != invalid_lock: fail("package doctor must not rewrite an invalid lock format version") - invalid_release_lock = lock.replace("version: v1.18.26", "version: v1.18.25", 1) + invalid_release_lock = lock.replace("version: v2026.10.20.155641", "version: v1.18.25", 1) (work / "claro.lock").write_text(invalid_release_lock, encoding="utf-8") rc, out = run([str(EXE), "package", "doctor"], work) if rc == 0: fail("package doctor must reject a lockfile for a different Claro release") - if "BAD lock release version: expected exactly one version: v1.18.26 line" not in out: + if "BAD lock release version: expected exactly one version: v2026.10.20.155641 line" not in out: fail(f"Lock release-version diagnostic was unclear:\n{out}") if read(work / "claro.lock") != invalid_release_lock: fail("package doctor must not rewrite an invalid lock release version") @@ -343,7 +343,7 @@ def main(): fail("Long package name should not create a package folder") (work / "claro.project").write_text( - "manifest-version: 1\nname: ClaroProject\nmain: main.claro\nversion: v1.18.26\npackages:\npackage: ../bad\n", + "manifest-version: 1\nname: ClaroProject\nmain: main.claro\nversion: v2026.10.20.155641\npackages:\npackage: ../bad\n", encoding="utf-8", ) rc, out = run([str(EXE), "package", "doctor"], work) @@ -382,7 +382,7 @@ def main(): fail("package add should not create a package folder while unsafe names remain in claro.project") (work / "claro.project").write_text( - "manifest-version: 1\nname: ClaroProject\nmain: main.claro\nversion: v1.18.26\npackages:\npackage: math-tools\npackage: ../bad\n", + "manifest-version: 1\nname: ClaroProject\nmain: main.claro\nversion: v2026.10.20.155641\npackages:\npackage: math-tools\npackage: ../bad\n", encoding="utf-8", ) rc, out = run([str(EXE), "package", "remove", "math-tools"], work) diff --git a/tools/validate_v1_16.py b/tools/validate_v1_16.py index 1266a54..a89c83d 100644 --- a/tools/validate_v1_16.py +++ b/tools/validate_v1_16.py @@ -43,4 +43,4 @@ with tempfile.TemporaryDirectory() as td: run([str(local), "package", "remove", "text"], cwd=proj) run([str(local), "run"], cwd=proj) -print("Validation passed. Claro v1.18.26 networking reliability is ready for use.") +print("Validation passed. Claro v2026.10.20.155641 networking reliability is ready for use.") diff --git a/tools/validate_v1_17.py b/tools/validate_v1_17.py index 0f8f05d..a83f99a 100644 --- a/tools/validate_v1_17.py +++ b/tools/validate_v1_17.py @@ -38,7 +38,7 @@ def build(): def main(): build() out = run([str(EXE), "--version"]) - if "Claro v1.18.26" not in out: + if "Claro v2026.10.20.155641" not in out: raise SystemExit("Wrong version string") run([str(EXE), "test"]) run([str(EXE), "validate"]) @@ -55,7 +55,7 @@ def main(): run([sys.executable, "tools/validate_write_file_expression_cleanup.py"]) run([sys.executable, "tools/validate_version_convention.py"]) run([str(EXE), "examples/networking.claro"]) - print("v1.18.26 validation complete") + print("v2026.10.20.155641 validation complete") if __name__ == "__main__": main() diff --git a/tools/validate_version_convention.py b/tools/validate_version_convention.py index abf80ca..6071565 100644 --- a/tools/validate_version_convention.py +++ b/tools/validate_version_convention.py @@ -1,11 +1,13 @@ #!/usr/bin/env python3 import json +import re +from datetime import datetime import subprocess from pathlib import Path ROOT = Path(__file__).resolve().parents[1] EXE = ROOT / "claro.exe" -EXPECTED = "Claro v1.18.26" +EXPECTED = "Claro v2026.10.20.155641" def fail(message): @@ -20,18 +22,21 @@ def main(): if not EXE.exists(): fail(f"Missing executable: {EXE}") out = run([str(EXE), "--version"]).stdout.strip() + if not re.fullmatch(r"Claro v\d{4}\.\d{2}\.\d{2}\.\d{6}", out): + fail(f"Expected UTC run timestamp Claro vYYYY.MM.DD.HHMMSS, got {out!r}") + datetime.strptime(out.removeprefix("Claro v"), "%Y.%m.%d.%H%M%S") if out != EXPECTED: fail(f"Expected version {EXPECTED!r}, got {out!r}") ide = run([str(EXE), "ide"]) if ide.returncode != 0: fail(ide.stdout) metadata = json.loads(ide.stdout) - if metadata.get("version") != "v1.18.26": - fail(f"Expected IDE metadata version 'v1.18.26', got {metadata.get('version')!r}") + if metadata.get("version") != "v2026.10.20.155641": + fail(f"Expected IDE metadata version 'v2026.10.20.155641', got {metadata.get('version')!r}") validator = ROOT / "tools" / "validate_v1_17.py" text = validator.read_text(encoding="utf-8") - if "Claro v1.18.26" not in text: - fail("tools/validate_v1_17.py must validate Claro v1.18.26") + if "Claro v2026.10.20.155641" not in text: + fail("tools/validate_v1_17.py must validate Claro v2026.10.20.155641") print("Version convention validation OK")