diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 47ef799..57fa1ba 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -95,9 +95,20 @@ jobs: - name: Run tests (nextest) # thread-language's napi-* features conflict with tree-sitter-parsing at runtime; # run it separately with only the compatible feature set. + # On Linux and Windows, run all tests (Docker is available for testcontainers). + if: runner.os != 'macOS' run: | cargo nextest run --workspace --exclude thread-language --all-features --no-fail-fast cargo nextest run -p thread-language --features all-parsers,matching --no-fail-fast + - name: Run tests (nextest, macOS - skip Docker-dependent tests) + # macOS GitHub Actions runners (Apple Silicon) do not have Docker, + # so we exclude the incremental_postgres_tests that use testcontainers + # to spin up Postgres containers. + if: runner.os == 'macOS' + run: | + cargo nextest run --workspace --exclude thread-language --all-features --no-fail-fast \ + -E "not (package(thread-flow) and binary(incremental_postgres_tests))" + cargo nextest run -p thread-language --features all-parsers,matching --no-fail-fast - name: Run doc tests run: | cargo test --doc --workspace --exclude thread-language --all-features @@ -119,6 +130,133 @@ jobs: uses: Swatinem/rust-cache@v2 with: cache-on-failure: true + - name: Install clang for WASM C compilation + run: sudo apt-get update -qq && sudo apt-get install -y clang + - name: Set up C header stubs for wasm32-unknown-unknown + # tree-sitter 0.25+ compiles C sources that #include standard C library headers + # (stdio.h, stdlib.h, string.h, etc.). The wasm32-unknown-unknown target has no OS + # and therefore no libc headers. We provide minimal stubs so the code compiles; + # actual implementations (malloc, memcpy, etc.) come from the wasm runtime / Rust. + run: | + CLANG_RES=$(clang --print-resource-dir) + STUBS=/tmp/wasm-stubs + mkdir -p "$STUBS" + + # Use Python to write the stub files cleanly without heredoc indentation issues. + python3 - << 'PYEOF' +import os +STUBS = '/tmp/wasm-stubs' +stubs = {} + +stubs['stdio.h'] = """\ +#pragma once +#include +#include +typedef void FILE; +#define stderr ((FILE*)0) +#define stdout ((FILE*)0) +#define stdin ((FILE*)0) +#define EOF (-1) +static inline int fprintf(FILE *f, const char *fmt, ...) { (void)f; (void)fmt; return 0; } +static inline int vfprintf(FILE *f, const char *fmt, va_list ap) { (void)f; (void)fmt; (void)ap; return 0; } +static inline int printf(const char *fmt, ...) { (void)fmt; return 0; } +static inline int snprintf(char *s, size_t n, const char *fmt, ...) { (void)s; (void)n; (void)fmt; return 0; } +static inline int sprintf(char *s, const char *fmt, ...) { (void)s; (void)fmt; return 0; } +static inline int fputc(int c, FILE *f) { (void)f; return c; } +static inline int fputs(const char *s, FILE *f) { (void)s; (void)f; return 0; } +static inline int fflush(FILE *f) { (void)f; return 0; } +""" + +stubs['stdlib.h'] = """\ +#pragma once +#include +#define EXIT_SUCCESS 0 +#define EXIT_FAILURE 1 +extern void *malloc(size_t size); +extern void *calloc(size_t count, size_t size); +extern void *realloc(void *ptr, size_t size); +extern void free(void *ptr); +extern void abort(void); +extern void exit(int status); +static inline int abs(int x) { return x < 0 ? -x : x; } +""" + +stubs['string.h'] = """\ +#pragma once +#include +extern void *memcpy(void *dest, const void *src, size_t n); +extern void *memmove(void *dest, const void *src, size_t n); +extern void *memset(void *s, int c, size_t n); +extern int memcmp(const void *s1, const void *s2, size_t n); +extern void *memchr(const void *s, int c, size_t n); +extern size_t strlen(const char *s); +extern int strcmp(const char *s1, const char *s2); +extern int strncmp(const char *s1, const char *s2, size_t n); +extern char *strcpy(char *dest, const char *src); +extern char *strncpy(char *dest, const char *src, size_t n); +extern char *strcat(char *dest, const char *src); +extern char *strncat(char *dest, const char *src, size_t n); +extern char *strchr(const char *s, int c); +extern char *strrchr(const char *s, int c); +extern char *strstr(const char *haystack, const char *needle); +""" + +stubs['time.h'] = """\ +#pragma once +#include +typedef uint64_t time_t; +typedef uint64_t clock_t; +#define CLOCKS_PER_SEC ((clock_t)1000000) +static inline time_t time(time_t *t) { if (t) *t = 0; return 0; } +static inline clock_t clock(void) { return 0; } +""" + +stubs['ctype.h'] = """\ +#pragma once +static inline int isalpha(int c) { return (c>='a'&&c<='z')||(c>='A'&&c<='Z'); } +static inline int isdigit(int c) { return c>='0'&&c<='9'; } +static inline int isalnum(int c) { return isalpha(c)||isdigit(c); } +static inline int isspace(int c) { return c==' '||c=='\\t'||c=='\\n'||c=='\\r'||c=='\\f'||c=='\\v'; } +static inline int isupper(int c) { return c>='A'&&c<='Z'; } +static inline int islower(int c) { return c>='a'&&c<='z'; } +static inline int toupper(int c) { return islower(c)?c-('a'-'A'):c; } +static inline int tolower(int c) { return isupper(c)?c+('a'-'A'):c; } +static inline int isprint(int c) { return c>=' '&&c<='~'; } +static inline int iscntrl(int c) { return c<' '||c==127; } +static inline int isxdigit(int c) { return isdigit(c)||(c>='a'&&c<='f')||(c>='A'&&c<='F'); } +static inline int ispunct(int c) { return isprint(c)&&!isalnum(c)&&c!=' '; } +""" + +stubs['wctype.h'] = """\ +#pragma once +#include +typedef uint32_t wint_t; +#define WEOF ((wint_t)-1) +static inline int iswspace(wint_t c) { return c==' '||c=='\\t'||c=='\\n'||c=='\\r'||c=='\\f'||c=='\\v'; } +static inline int iswalpha(wint_t c) { return (c>='a'&&c<='z')||(c>='A'&&c<='Z'); } +static inline int iswdigit(wint_t c) { return c>='0'&&c<='9'; } +static inline int iswalnum(wint_t c) { return iswalpha(c)||iswdigit(c); } +static inline int iswupper(wint_t c) { return c>='A'&&c<='Z'; } +static inline int iswlower(wint_t c) { return c>='a'&&c<='z'; } +static inline int iswprint(wint_t c) { return c>=' '&&c<='~'; } +static inline int iswpunct(wint_t c) { return iswprint(c)&&!iswalnum(c)&&c!=' '; } +static inline wint_t towlower(wint_t c) { return iswupper(c)?c+('a'-'A'):c; } +static inline wint_t towupper(wint_t c) { return iswlower(c)?c-('a'-'A'):c; } +""" + +stubs['assert.h'] = """\ +#pragma once +#define assert(x) ((void)(x)) +""" + +for name, content in stubs.items(): + path = os.path.join(STUBS, name) + with open(path, 'w') as f: + f.write(content) + print(f' wrote {path}') +PYEOF + + echo "CFLAGS_wasm32_unknown_unknown=-I${STUBS} -I${CLANG_RES}/include" >> "$GITHUB_ENV" - name: Install wasm-pack uses: jetli/wasm-pack-action@v0.4.0 - name: Build WASM (dev) @@ -163,6 +301,9 @@ jobs: name: Security Audit needs: quick-checks runs-on: ubuntu-latest + permissions: + contents: read + checks: write steps: - uses: actions/checkout@v4 with: diff --git a/REUSE.toml b/REUSE.toml index faea6c2..02b4846 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -8,3 +8,33 @@ path = ["claudedocs/**", "crates/**/claudedocs/**"] precedence = "aggregate" SPDX-FileCopyrightText = "2025 Knitli Inc. " SPDX-License-Identifier = "MIT OR Apache-2.0" + +[[annotations]] +path = [".claude/plugins/**", "ctx-plugin/**"] +precedence = "aggregate" +SPDX-FileCopyrightText = "2025 Knitli Inc. " +SPDX-License-Identifier = "MIT OR Apache-2.0" + +[[annotations]] +path = [".vscode/**"] +precedence = "aggregate" +SPDX-FileCopyrightText = "2025 Knitli Inc. " +SPDX-License-Identifier = "MIT OR Apache-2.0" + +[[annotations]] +path = ["crates/flow/benches/bench_graph_traversal.rs"] +precedence = "aggregate" +SPDX-FileCopyrightText = "2025 Knitli Inc. " +SPDX-License-Identifier = "AGPL-3.0-or-later" + +[[annotations]] +path = ["get_comments.js"] +precedence = "aggregate" +SPDX-FileCopyrightText = "2025 Knitli Inc. " +SPDX-License-Identifier = "MIT OR Apache-2.0" + +[[annotations]] +path = ["specs/**"] +precedence = "aggregate" +SPDX-FileCopyrightText = "2025 Knitli Inc. " +SPDX-License-Identifier = "MIT OR Apache-2.0" diff --git a/crates/ast-engine/src/tree_sitter/mod.rs b/crates/ast-engine/src/tree_sitter/mod.rs index caa22e8..cad343d 100644 --- a/crates/ast-engine/src/tree_sitter/mod.rs +++ b/crates/ast-engine/src/tree_sitter/mod.rs @@ -145,6 +145,7 @@ fn parse_lang( /// /// ```rust,no_run /// # use thread_ast_engine::tree_sitter::StrDoc; +/// # use thread_ast_engine::Doc; /// # #[derive(Clone, Debug)] /// # struct JavaScript; /// # impl thread_ast_engine::Language for JavaScript { diff --git a/crates/flow/benches/bench_graph_traversal.rs.license b/crates/flow/benches/bench_graph_traversal.rs.license new file mode 100644 index 0000000..7aeb473 --- /dev/null +++ b/crates/flow/benches/bench_graph_traversal.rs.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2025 Knitli Inc. + +SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/crates/flow/src/incremental/graph.rs b/crates/flow/src/incremental/graph.rs index a93b839..4408f2e 100644 --- a/crates/flow/src/incremental/graph.rs +++ b/crates/flow/src/incremental/graph.rs @@ -260,7 +260,7 @@ impl DependencyGraph { /// )); /// /// // Change C -> affects B and A - /// let changed = RapidSet::from([PathBuf::from("C")]); + /// let changed: RapidSet = [PathBuf::from("C")].into_iter().collect(); /// let affected = graph.find_affected_files(&changed); /// assert!(affected.contains(&PathBuf::from("A"))); /// assert!(affected.contains(&PathBuf::from("B"))); @@ -325,9 +325,9 @@ impl DependencyGraph { /// PathBuf::from("B"), PathBuf::from("C"), DependencyType::Import, /// )); /// - /// let files = RapidSet::from([ + /// let files: RapidSet = [ /// PathBuf::from("A"), PathBuf::from("B"), PathBuf::from("C"), - /// ]); + /// ].into_iter().collect(); /// let sorted = graph.topological_sort(&files).unwrap(); /// // C should come before B, B before A /// let pos_a = sorted.iter().position(|p| p == &PathBuf::from("A")).unwrap(); diff --git a/crates/flow/src/incremental/invalidation.rs b/crates/flow/src/incremental/invalidation.rs index ad66e94..da9ac9c 100644 --- a/crates/flow/src/incremental/invalidation.rs +++ b/crates/flow/src/incremental/invalidation.rs @@ -45,7 +45,6 @@ pub enum InvalidationError { /// ```rust /// use thread_flow::incremental::invalidation::InvalidationDetector; /// use thread_flow::incremental::DependencyGraph; -/// use thread_utilities::RapishSet; /// use std::path::PathBuf; /// /// let graph = DependencyGraph::new(); diff --git a/crates/flow/src/incremental/mod.rs b/crates/flow/src/incremental/mod.rs index d79df4d..ec34e91 100644 --- a/crates/flow/src/incremental/mod.rs +++ b/crates/flow/src/incremental/mod.rs @@ -36,8 +36,8 @@ //! AnalysisDefFingerprint, DependencyEdge, DependencyType, //! }; //! use thread_flow::incremental::graph::DependencyGraph; +//! use thread_utilities::RapidSet; //! use std::path::PathBuf; -//! use std::collections::HashSet; //! //! // Create a dependency graph //! let mut graph = DependencyGraph::new(); @@ -51,7 +51,7 @@ //! }); //! //! // Find files affected by a change to utils.rs -//! let changed = HashSet::from([PathBuf::from("src/utils.rs")]); +//! let changed: RapidSet = [PathBuf::from("src/utils.rs")].into_iter().collect(); //! let affected = graph.find_affected_files(&changed); //! assert!(affected.contains(&PathBuf::from("src/main.rs"))); //! ``` diff --git a/crates/flow/src/incremental/types.rs b/crates/flow/src/incremental/types.rs index 71db458..3949533 100644 --- a/crates/flow/src/incremental/types.rs +++ b/crates/flow/src/incremental/types.rs @@ -220,7 +220,7 @@ impl AnalysisDefFingerprint { /// use thread_utilities::RapidSet; /// use std::path::PathBuf; /// - /// let sources = RapidSet::from([PathBuf::from("dep.rs")]); + /// let sources: RapidSet = [PathBuf::from("dep.rs")].into_iter().collect(); /// let fp = AnalysisDefFingerprint::with_sources(b"content", sources); /// assert_eq!(fp.source_files.len(), 1); /// ``` diff --git a/ctx-plugin/.claude-plugin/plugin.json.license b/ctx-plugin/.claude-plugin/plugin.json.license new file mode 100644 index 0000000..8b045c4 --- /dev/null +++ b/ctx-plugin/.claude-plugin/plugin.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2025 Knitli Inc. + +SPDX-License-Identifier: MIT OR Apache-2.0 diff --git a/ctx-plugin/PLAN.md.license b/ctx-plugin/PLAN.md.license new file mode 100644 index 0000000..8b045c4 --- /dev/null +++ b/ctx-plugin/PLAN.md.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2025 Knitli Inc. + +SPDX-License-Identifier: MIT OR Apache-2.0 diff --git a/ctx-plugin/README.md.license b/ctx-plugin/README.md.license new file mode 100644 index 0000000..8b045c4 --- /dev/null +++ b/ctx-plugin/README.md.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2025 Knitli Inc. + +SPDX-License-Identifier: MIT OR Apache-2.0 diff --git a/ctx-plugin/agents/claim-validator.md.license b/ctx-plugin/agents/claim-validator.md.license new file mode 100644 index 0000000..8b045c4 --- /dev/null +++ b/ctx-plugin/agents/claim-validator.md.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2025 Knitli Inc. + +SPDX-License-Identifier: MIT OR Apache-2.0 diff --git a/ctx-plugin/agents/context-auditor.md.license b/ctx-plugin/agents/context-auditor.md.license new file mode 100644 index 0000000..8b045c4 --- /dev/null +++ b/ctx-plugin/agents/context-auditor.md.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2025 Knitli Inc. + +SPDX-License-Identifier: MIT OR Apache-2.0 diff --git a/ctx-plugin/commands/ctx-check.md.license b/ctx-plugin/commands/ctx-check.md.license new file mode 100644 index 0000000..8b045c4 --- /dev/null +++ b/ctx-plugin/commands/ctx-check.md.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2025 Knitli Inc. + +SPDX-License-Identifier: MIT OR Apache-2.0 diff --git a/ctx-plugin/commands/ctx-discover.md.license b/ctx-plugin/commands/ctx-discover.md.license new file mode 100644 index 0000000..8b045c4 --- /dev/null +++ b/ctx-plugin/commands/ctx-discover.md.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2025 Knitli Inc. + +SPDX-License-Identifier: MIT OR Apache-2.0 diff --git a/ctx-plugin/commands/ctx-drift.md.license b/ctx-plugin/commands/ctx-drift.md.license new file mode 100644 index 0000000..8b045c4 --- /dev/null +++ b/ctx-plugin/commands/ctx-drift.md.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2025 Knitli Inc. + +SPDX-License-Identifier: MIT OR Apache-2.0 diff --git a/ctx-plugin/commands/ctx-fix.md.license b/ctx-plugin/commands/ctx-fix.md.license new file mode 100644 index 0000000..8b045c4 --- /dev/null +++ b/ctx-plugin/commands/ctx-fix.md.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2025 Knitli Inc. + +SPDX-License-Identifier: MIT OR Apache-2.0 diff --git a/ctx-plugin/commands/ctx.md.license b/ctx-plugin/commands/ctx.md.license new file mode 100644 index 0000000..8b045c4 --- /dev/null +++ b/ctx-plugin/commands/ctx.md.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2025 Knitli Inc. + +SPDX-License-Identifier: MIT OR Apache-2.0 diff --git a/ctx-plugin/cross-client/.codex/SKILL.md.license b/ctx-plugin/cross-client/.codex/SKILL.md.license new file mode 100644 index 0000000..8b045c4 --- /dev/null +++ b/ctx-plugin/cross-client/.codex/SKILL.md.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2025 Knitli Inc. + +SPDX-License-Identifier: MIT OR Apache-2.0 diff --git a/ctx-plugin/cross-client/.cursor/rules/ctx-audit.mdc.license b/ctx-plugin/cross-client/.cursor/rules/ctx-audit.mdc.license new file mode 100644 index 0000000..8b045c4 --- /dev/null +++ b/ctx-plugin/cross-client/.cursor/rules/ctx-audit.mdc.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2025 Knitli Inc. + +SPDX-License-Identifier: MIT OR Apache-2.0 diff --git a/ctx-plugin/cross-client/AGENTS.md.license b/ctx-plugin/cross-client/AGENTS.md.license new file mode 100644 index 0000000..8b045c4 --- /dev/null +++ b/ctx-plugin/cross-client/AGENTS.md.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2025 Knitli Inc. + +SPDX-License-Identifier: MIT OR Apache-2.0 diff --git a/ctx-plugin/hooks/session-stop-reminder.md.license b/ctx-plugin/hooks/session-stop-reminder.md.license new file mode 100644 index 0000000..8b045c4 --- /dev/null +++ b/ctx-plugin/hooks/session-stop-reminder.md.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2025 Knitli Inc. + +SPDX-License-Identifier: MIT OR Apache-2.0 diff --git a/ctx-plugin/install.sh.license b/ctx-plugin/install.sh.license new file mode 100644 index 0000000..8b045c4 --- /dev/null +++ b/ctx-plugin/install.sh.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2025 Knitli Inc. + +SPDX-License-Identifier: MIT OR Apache-2.0 diff --git a/ctx-plugin/skills/context-hygiene.md.license b/ctx-plugin/skills/context-hygiene.md.license new file mode 100644 index 0000000..8b045c4 --- /dev/null +++ b/ctx-plugin/skills/context-hygiene.md.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2025 Knitli Inc. + +SPDX-License-Identifier: MIT OR Apache-2.0 diff --git a/get_comments.js.license b/get_comments.js.license new file mode 100644 index 0000000..8b045c4 --- /dev/null +++ b/get_comments.js.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2025 Knitli Inc. + +SPDX-License-Identifier: MIT OR Apache-2.0 diff --git a/specs/thread-context-doctor-spec.md.license b/specs/thread-context-doctor-spec.md.license new file mode 100644 index 0000000..8b045c4 --- /dev/null +++ b/specs/thread-context-doctor-spec.md.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2025 Knitli Inc. + +SPDX-License-Identifier: MIT OR Apache-2.0