diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..fedc5dd --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,3 @@ +# Displays the "Sponsor" button on this repository. +# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/displaying-a-sponsor-button-in-your-repository +github: [ix-infrastructure] diff --git a/.github/scripts/copyright-headers.py b/.github/scripts/copyright-headers.py new file mode 100644 index 0000000..4e7eb4a --- /dev/null +++ b/.github/scripts/copyright-headers.py @@ -0,0 +1,156 @@ +# Copyright 2026 Ix Infrastructure Inc. + +"""Enforce the Ix Infrastructure copyright header on every source file. + + python3 .github/scripts/copyright-headers.py # check, exit 1 on misses + python3 .github/scripts/copyright-headers.py --fix # insert the missing ones + +Run from the repository root. CI runs the check; contributors run --fix. + +Deliberately NOT covered, because a header there is wrong rather than missing: + * test fixtures — parser tests snapshot their output including line numbers, + so a header shifts every symbol down a line and breaks them; + * anything the repo marks linguist-generated in .gitattributes — regenerated + by tooling (the header would be clobbered) or vendored from upstream, whose + copyright is not ours to claim; + * committed build output and dependencies (dist/, node_modules/, ...). + +A file type absent from the tables below is not checked. Adding a language to +the repo means adding its comment syntax here. +""" + +import os +import re +import subprocess +import sys + +HEADER = "Copyright 2026 Ix Infrastructure Inc." + +SLASH = {".ts", ".tsx", ".mts", ".cts", ".mjs", ".cjs", ".js", ".jsx", ".scala", ".sc", + ".java", ".go", ".rs", ".c", ".h", ".cc", ".cpp", ".hpp", ".swift", ".kt"} +HASH = {".sh", ".bash", ".zsh", ".ps1", ".psm1", ".py", ".rb", ".pl"} +CMD = {".cmd", ".bat"} + +EXCLUDE_RE = re.compile( + r"(^|/)(node_modules|dist|build|out|target|vendor|third_party|\.yarn|coverage)(/|$)" + r"|(^|/)(test-)?fixtures?(/|$)" + r"|(^|/)__fixtures__(/|$)" + r"|\.min\.(js|mjs|cjs)$" + r"|\.d\.ts$" +) + +SHEBANG_RE = re.compile(r"^#!") +CODING_RE = re.compile(r"^#.*coding[:=]") +ECHOOFF_RE = re.compile(r"^\s*@echo\s+off", re.I) + + +def comment(ext): + if ext in SLASH: + return "// " + HEADER + if ext in HASH: + return "# " + HEADER + if ext in CMD: + return "@REM " + HEADER + return None + + +def tracked_files(): + out = subprocess.run(["git", "ls-files"], capture_output=True, text=True, check=True).stdout + return [p for p in out.splitlines() if p] + + +def linguist_generated(paths): + """Files the repo itself declares generated. Let git do the glob matching.""" + if not paths: + return set() + proc = subprocess.run(["git", "check-attr", "--stdin", "linguist-generated"], + input="\n".join(paths), capture_output=True, text=True) + return {line.rsplit(": linguist-generated:", 1)[0] + for line in proc.stdout.splitlines() if line.endswith(": set")} + + +def insert_at(lines, ext): + """Index the header goes at, keeping order-sensitive first lines in place.""" + if not lines: + return 0 + if ext in CMD: + # `@echo off` must stay first or the REM echoes to the console + return 1 if ECHOOFF_RE.match(lines[0]) else 0 + if SHEBANG_RE.match(lines[0]): + if ext == ".py" and len(lines) > 1 and CODING_RE.match(lines[1]): + return 2 + return 1 + if ext == ".py" and CODING_RE.match(lines[0]): + return 1 + return 0 + + +def main(fix): + files = tracked_files() + generated = linguist_generated(files) + missing, wrong = [], [] + + for rel in files: + ext = os.path.splitext(rel)[1] + line = comment(ext) + if line is None or EXCLUDE_RE.search(rel) or rel in generated: + continue + if not os.path.isfile(rel) or os.path.islink(rel): + continue + with open(rel, "r", encoding="utf-8", errors="surrogateescape", newline="") as fh: + text = fh.read() + if not text.strip(): + continue + lines = text.split("\n") + head = [candidate.rstrip("\r").rstrip() for candidate in lines[:5]] + if line in head: + continue + if any("Copyright" in candidate for candidate in head): + # A copyright line, but not ours verbatim — a stale spelling of the + # entity, or someone else's claim. Never auto-"fixed": inserting a + # second header would leave the file asserting two owners. + wrong.append(rel) + continue + missing.append(rel) + if fix: + nl_crlf = "\r\n" in text.split("\n", 1)[0] + "\n" + block = [line, ""] + if nl_crlf: + block = [b + "\r" for b in block] + at = insert_at([candidate.rstrip("\r") for candidate in lines], ext) + lines[at:at] = block + with open(rel, "w", encoding="utf-8", errors="surrogateescape", newline="") as fh: + fh.write("\n".join(lines)) + + if not missing and not wrong: + print(f"All source files carry the header: {HEADER}") + return 0 + + if wrong: + print(f"{len(wrong)} source file(s) carry a copyright line that is not" + f' exactly "{HEADER}":\n') + for w in wrong: + print(f" {w}") + print("\nFix these by hand — --fix will not touch them, because inserting a" + "\nsecond header would leave the file naming two owners.\n") + + if missing: + if fix: + print(f"Added the header to {len(missing)} file(s):") + for m in missing: + print(f" {m}") + else: + print(f"{len(missing)} source file(s) are missing the copyright header:\n") + for m in missing: + print(f" {m}") + print(f'\nEvery source file must start with "{HEADER}"' + " in that language's comment syntax.\nFix them all with:\n" + "\n python3 .github/scripts/copyright-headers.py --fix\n") + + if fix and not wrong: + return 0 + return 1 + + +if __name__ == "__main__": + sys.exit(main("--fix" in sys.argv)) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6e99c53..6655d3e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,6 +30,14 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false + + # Every source file must carry the copyright line. In this job rather than + # a workflow of its own: the ruleset requires exactly one status check, + # `CI Passed`, and `test` is in its `needs` — a check in any other + # workflow reports on the PR and gates nothing. Toolchain-free (stdlib + # python3 is preinstalled; the script shells out only to git). + - name: Copyright headers on every source file + run: python3 .github/scripts/copyright-headers.py - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 with: bun-version: latest diff --git a/LICENSE b/LICENSE index 261eeb9..4e9ddc4 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [yyyy] [name of copyright owner] + Copyright 2026 Ix Infrastructure Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index 0275d6f..731573e 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # ix-opencode-plugin +[![Sponsor](https://img.shields.io/badge/sponsor-%E2%9D%A4-db61a2)](https://github.com/sponsors/ix-infrastructure) + An OpenCode plugin that brings [Ix Memory](https://github.com/ix-infrastructure/Ix)'s graph-first reasoning into OpenCode as a native cognitive layer. OpenCode + Ix = reasoning engine + persistent code knowledge graph. Skills are cognitive abstractions — not CLI wrappers — that start with cheap graph signals before reading source, and stop early when the question is answered. diff --git a/install.ps1 b/install.ps1 index f10c3da..8ecfd3d 100644 --- a/install.ps1 +++ b/install.ps1 @@ -1,3 +1,5 @@ +# Copyright 2026 Ix Infrastructure Inc. + # install.ps1 — ix-opencode-plugin installer for Windows # # Usage: diff --git a/install.sh b/install.sh index c5d0552..0c5ea2c 100755 --- a/install.sh +++ b/install.sh @@ -1,4 +1,6 @@ #!/usr/bin/env bash +# Copyright 2026 Ix Infrastructure Inc. + # install.sh — ix-opencode-plugin installer # # Usage: diff --git a/plugins/ix-plugin.ts b/plugins/ix-plugin.ts index c13ee9c..ae9aa89 100644 --- a/plugins/ix-plugin.ts +++ b/plugins/ix-plugin.ts @@ -1,3 +1,5 @@ +// Copyright 2026 Ix Infrastructure Inc. + /** * ix-plugin.ts — OpenCode plugin entry point (v1.4.2 format) * diff --git a/runtime/cli.ts b/runtime/cli.ts index e35b9fa..e9d3d40 100644 --- a/runtime/cli.ts +++ b/runtime/cli.ts @@ -1,3 +1,5 @@ +// Copyright 2026 Ix Infrastructure Inc. + import { $ } from "bun"; /** diff --git a/runtime/client.ts b/runtime/client.ts index 6d6ae86..3d373de 100644 --- a/runtime/client.ts +++ b/runtime/client.ts @@ -1,3 +1,5 @@ +// Copyright 2026 Ix Infrastructure Inc. + /** * Ix Core Runtime HTTP client. * diff --git a/runtime/llm.ts b/runtime/llm.ts index c990fcb..043d320 100644 --- a/runtime/llm.ts +++ b/runtime/llm.ts @@ -1,3 +1,5 @@ +// Copyright 2026 Ix Infrastructure Inc. + /** * ix `--format llm` fast-path, gated on the installed CLI's version. * diff --git a/runtime/secrets.ts b/runtime/secrets.ts index 04d2f04..2337233 100644 --- a/runtime/secrets.ts +++ b/runtime/secrets.ts @@ -1,3 +1,5 @@ +// Copyright 2026 Ix Infrastructure Inc. + /** * Secret detection and redaction for Ix runtime payloads. * diff --git a/tests/llm.test.ts b/tests/llm.test.ts index 8246249..0f5997d 100644 --- a/tests/llm.test.ts +++ b/tests/llm.test.ts @@ -1,3 +1,5 @@ +// Copyright 2026 Ix Infrastructure Inc. + /** * The `--format llm` gate. * diff --git a/tests/tools.test.ts b/tests/tools.test.ts index 0679039..b945e21 100644 --- a/tests/tools.test.ts +++ b/tests/tools.test.ts @@ -1,3 +1,5 @@ +// Copyright 2026 Ix Infrastructure Inc. + /** * OpenCodeToolContractParity + RuntimeUnavailableFallback + BunCompatibility * diff --git a/tools/ix-decide.ts b/tools/ix-decide.ts index 01621d0..cde75c1 100644 --- a/tools/ix-decide.ts +++ b/tools/ix-decide.ts @@ -1,3 +1,5 @@ +// Copyright 2026 Ix Infrastructure Inc. + /** * ix-decide — pre-edit policy gate * diff --git a/tools/ix-docs-tool.ts b/tools/ix-docs-tool.ts index 2c8aaa9..c6313d6 100644 --- a/tools/ix-docs-tool.ts +++ b/tools/ix-docs-tool.ts @@ -1,3 +1,5 @@ +// Copyright 2026 Ix Infrastructure Inc. + /** * ix-docs-tool — doc/context summary retrieval * diff --git a/tools/ix-explain.ts b/tools/ix-explain.ts index 6fd5445..16feab5 100644 --- a/tools/ix-explain.ts +++ b/tools/ix-explain.ts @@ -1,3 +1,5 @@ +// Copyright 2026 Ix Infrastructure Inc. + /** * ix-explain — symbol deep explanation * diff --git a/tools/ix-health.ts b/tools/ix-health.ts index 4d09a3a..d3852db 100644 --- a/tools/ix-health.ts +++ b/tools/ix-health.ts @@ -1,3 +1,5 @@ +// Copyright 2026 Ix Infrastructure Inc. + /** * ix-health — CLI and graph availability probe * diff --git a/tools/ix-history.ts b/tools/ix-history.ts index 35e860d..00cae3a 100644 --- a/tools/ix-history.ts +++ b/tools/ix-history.ts @@ -1,3 +1,5 @@ +// Copyright 2026 Ix Infrastructure Inc. + /** * ix-history — revision and history lookup * diff --git a/tools/ix-impact.ts b/tools/ix-impact.ts index 0d0715c..23ad4d8 100644 --- a/tools/ix-impact.ts +++ b/tools/ix-impact.ts @@ -1,3 +1,5 @@ +// Copyright 2026 Ix Infrastructure Inc. + /** * ix-impact — blast radius analysis * diff --git a/tools/ix-ingest.ts b/tools/ix-ingest.ts index 4861a02..e903161 100644 --- a/tools/ix-ingest.ts +++ b/tools/ix-ingest.ts @@ -1,3 +1,5 @@ +// Copyright 2026 Ix Infrastructure Inc. + /** * ix-ingest — ingest status and trigger * diff --git a/tools/ix-inventory.ts b/tools/ix-inventory.ts index 18c473b..166b01b 100644 --- a/tools/ix-inventory.ts +++ b/tools/ix-inventory.ts @@ -1,3 +1,5 @@ +// Copyright 2026 Ix Infrastructure Inc. + /** * ix-inventory — enumerate files or symbols within a path scope * diff --git a/tools/ix-locate.ts b/tools/ix-locate.ts index 5a1fa93..ab71403 100644 --- a/tools/ix-locate.ts +++ b/tools/ix-locate.ts @@ -1,3 +1,5 @@ +// Copyright 2026 Ix Infrastructure Inc. + /** * ix-locate — text / semantic search * diff --git a/tools/ix-map.ts b/tools/ix-map.ts index d817333..b7a5517 100644 --- a/tools/ix-map.ts +++ b/tools/ix-map.ts @@ -1,3 +1,5 @@ +// Copyright 2026 Ix Infrastructure Inc. + /** * ix-map — architectural map and subsystem overview * diff --git a/tools/ix-neighbors.ts b/tools/ix-neighbors.ts index 74fc7ee..b6b2a51 100644 --- a/tools/ix-neighbors.ts +++ b/tools/ix-neighbors.ts @@ -1,3 +1,5 @@ +// Copyright 2026 Ix Infrastructure Inc. + /** * ix-neighbors — neighborhood traversal * diff --git a/tools/ix-query.ts b/tools/ix-query.ts index c1821e7..fa63be6 100644 --- a/tools/ix-query.ts +++ b/tools/ix-query.ts @@ -1,3 +1,5 @@ +// Copyright 2026 Ix Infrastructure Inc. + /** * ix-query — graph entity lookup * diff --git a/tools/ix-rank.ts b/tools/ix-rank.ts index 25d07a6..f58dbe4 100644 --- a/tools/ix-rank.ts +++ b/tools/ix-rank.ts @@ -1,3 +1,5 @@ +// Copyright 2026 Ix Infrastructure Inc. + /** * ix-rank — rank symbols by a graph metric * diff --git a/tools/ix-smells.ts b/tools/ix-smells.ts index d6d103b..9cfdcda 100644 --- a/tools/ix-smells.ts +++ b/tools/ix-smells.ts @@ -1,3 +1,5 @@ +// Copyright 2026 Ix Infrastructure Inc. + /** * ix-smells — architecture smell detection * diff --git a/tools/ix-stats.ts b/tools/ix-stats.ts index 9a67a39..49e1f8f 100644 --- a/tools/ix-stats.ts +++ b/tools/ix-stats.ts @@ -1,3 +1,5 @@ +// Copyright 2026 Ix Infrastructure Inc. + /** * ix-stats — graph-wide statistics * diff --git a/tools/ix-subsystems.ts b/tools/ix-subsystems.ts index bbe550a..7037df0 100644 --- a/tools/ix-subsystems.ts +++ b/tools/ix-subsystems.ts @@ -1,3 +1,5 @@ +// Copyright 2026 Ix Infrastructure Inc. + /** * ix-subsystems — list graph-derived subsystems * diff --git a/tools/ix-trace.ts b/tools/ix-trace.ts index 6212eab..a65769f 100644 --- a/tools/ix-trace.ts +++ b/tools/ix-trace.ts @@ -1,3 +1,5 @@ +// Copyright 2026 Ix Infrastructure Inc. + /** * ix-trace — execution path tracing *