Skip to content

Latest commit

 

History

History
245 lines (199 loc) · 31.6 KB

KNOWLEDGE.md

File metadata and controls

245 lines (199 loc) · 31.6 KB

Project Engineering Knowledge Base — GravityGuard

This document serves as the persistent engineering knowledge repository for GravityGuard. Any developer or AI agent modifying this codebase must adhere strictly to the invariants, patterns, and lessons documented here.


0. ALWAYS_LOAD (Core Invariants)

Important

Deterministic Airbag Protocol (Obligation-Driven Governance)

  1. Core Philosophy: GravityGuard is an ultra-fast (<10ms in-memory), deterministic airbag — NOT a heavy compiler or AST engine.
  2. P0 Hard Gates (ALWAYS BLOCK):
    • G0_SECRET_LEAK: Zero exposed API keys, tokens, or private credentials.
    • G1_SILENT_EXCEPTION: Zero empty catch/except blocks (except: pass, catch {}).
    • G2_TEST_INTEGRITY: Zero test deletions, skipping, or bypass escape-hatch injection.
  • G7_TS_FIRST: Application code must be authored in TypeScript (.ts/.tsx); raw .js/.cjs/.mjs files are blocked unless deterministically recognized as mandatory JS (dist/, �uild/, *.config.js, sw.js, *.worker.js) or justified via // @ts-mandatory: in the top 10 lines (logging feedback to gravityguard_js_feedback.jsonl). Workspace paths under ~/.gemini/antigravity/scratch/ strip their host prefix before is_vendor_or_cache evaluation.
  1. Obligations Before Session Stop (L3 Lifecycle Gate):
    • Test Obligation (T1): Modifying core production code requires matching test evidence before session stop. Translation dictionaries (lang-*, i18n), scratch files (scratch*), pure UI/DOM/TUI view files (/ui/, /views/, /components/, /web/, tui.ts, tree.ts), and CLI/launcher entrypoints (cli.ts, main.py, *_launcher.*) are exempt by default, and T3_SYMBOL_TO_TEST_LINK checks only exported/public API symbols rather than internal unexported helpers.
    • Doc Obligation (§6 Same-Commit Rule): Modifying engine/, src/, plugin/, or rules/ requires updating CHANGELOG.md (and docs/KNOWLEDGE.md if procedures change).
  2. Git Pre-Commit Gate (L4): tools/verify_doc_governance.py rejects commits altering engine/rules without staged documentation.

1. Architectural Invariants (Non-Negotiable)

Automatic documentation capture

  • Stop-time architecture synchronization prefers tools/generate_architecture.py; other projects use the built-in source inventory only when ARCHITECTURE.md already exists. The managed block is capped at 200 lines; handwritten sections are never truncated. Dependency/build directories and symlinks are excluded from the generic scan. An unchanged inventory does not rewrite the document.
  • Approved changes to .gravityguard.json, root/plugin hooks.json, files under rules/, *_rules.py, and doc_policy.py require CHANGELOG plus docs/KNOWLEDGE.md (preferred) or root KNOWLEDGE.md when present. Explicit enforceDocObligations: false still disables this tracking.
  • At Stop retry four, automatic CHANGELOG and [auto-ADR] entries can satisfy outstanding documentation obligations. Only successful insertions are resolved; missing documents and unrelated required documents remain pending. These factual file-change summaries do not replace human-authored architectural rationale.

Audit maintenance invariant

  • A blocked security operation is observed enforcement, not proof of abandonment or a recovered code defect. Keep security counts separate from code correction rates.
  • Never truncate the audit journal manually. tools/maintain_audit.py takes the writer lock, verifies private backup hashes and replaces the active stream with a durable checkpoint preserving sequence, cumulative counters and unresolved violation causality.
  • Deploy checkpoint-aware readers and writers before maintenance. Detailed reports and archives belong outside Git repositories. Never clear governance obligations, self-review records or fallback spools as log cleanup.

Tool schema compatibility invariant

  • Antigravity IDE's native edit_file tool (Path, Target, Replacement) replaces all occurrences by default (AllowMultiple=True), whereas MultiEdit chunks normalize via "edit" (AllowMultiple=False unless replace_all=True). diffing.py falls back to stripping trailing horizontal whitespace per line when exact match fails, and session_quarantine.py never records PROJECTION_UNSUPPORTED, TOOL_SCHEMA_UNSUPPORTED, or STATE_LOCK_TIMEOUT as policy-evasion targets.
  • G0 protection is a mandatory floor: project/user shadow preferences cannot disable it. Structured tool operations cannot mutate installed control files or runtime receipt/state files.
  • Projection failures are explicit denials. GRAVITYGUARD_TEST_VIRTUAL_FILES=1 exists only for synthetic legacy fixtures and must not be configured in production hooks.
  • Review governance is opt-in and uses unsigned self-review records under .gravityguard/self-review/<id>.json, bound to the candidate fingerprint. No review key (GRAVITYGUARD_REVIEW_KEY_FILE) or receipt_auth module exists any more; the check catches omission, not forgery.
  • UI advisory waivers use engine/governance_control.py and the engine lock. Direct TypeScript writes to governance JSON are forbidden. Mandatory review debts are preserved.
  • A built VSIX must pass verify_package.py's explicit allowlist and source parity checks before installation. No archives or local agent settings may ship.
  • File intent is normalized once in engine/gravityguard_engine/operations.py; adapters and rules must not add separate tool-name allowlists for file mutations.
  • A path alone does not prove a read. Empty strings are real values, including empty full writes and deletion replacements. Conflicting aliases, incomplete edit pairs and malformed edit lists must not silently become no-op diffs.
  • Catch-all hook registration covers tool renames, not arbitrary new schemas or missing host events. Unified patches, multi-file operations, URI paths, notebook semantics and shell execution require explicit adapters or separate enforcement. Never promise zero maintenance or a sandbox security boundary.

1.1. The Lightweight Gatekeeper Philosophy (Anti-Bloat Invariant)

  • GravityGuard is NOT a compiler or an AST-heavy SonarQube replacement.
  • The primary directive is: "Act as a deterministic airbag that prevents AI Coding Agents (Antigravity, Cursor, Claude Engineer) from degrading code architecture."
  • Latency Invariant:
    • Core in-memory rule evaluations (check_g1, check_g2, check_g4, difflib.SequenceMatcher) must finish in < 10 milliseconds (typically ~0.05–1.5 ms).
    • When invoked as an external process hook on Windows via CLI / tool hook, process spawn overhead (python.exe) adds real time and is host- and load-dependent: measured on this machine at a median of ~340–370 ms (observed range 294–558 ms across runs), well above the 100–220 ms figure quoted in earlier notes. The spawn cost is unavoidable and is not what the guard is optimised for; nothing else in the loop may block.
    • Sub-50ms claims apply strictly to internal rule logic, not full Windows subprocess spawn cycles.
    • Parity is claimed against git HEAD, not against an absolute number. Measure interleaved, in the same time slice; an absolute "0.11 ms" does not survive a change of host or load. See §4.3.
  • Heavy AST computations, recursive symbol resolutions, and whole-project semantic graphs are strictly prohibited. Prefer regex scanning, shallow AST inspection, file metrics, and import boundaries.

1.2. Zero Core IDE Tampering (Integrity Invariant)

  • Never inject scripts directly into host IDE HTML files (workbench.html, workbench-jetski-agent.html).
  • Modern VS Code and Antigravity distributions verify SHA-256 base64 checksums on startup via product.json. Direct file tampering triggers "Your installation appears to be corrupt".
  • All IDE UI elements must be mounted through official VS Code Extension APIs:
    • Status Bar: vscode.window.createStatusBarItem
    • Sidebar: vscode.window.registerWebviewViewProvider
    • Commands: vscode.commands.registerCommand

1.3. Dual Engine Coordination

  • TypeScript Layer (src/):
    • Handles VS Code / Antigravity lifecycle, webview message passing, Status Bar items, and clipboard synchronization.
    • src/intent.ts is the local intent classifier: deterministic, no network, <1ms. It is the reason the prompt pipeline has no runtime dependency on an AI gateway.
    • src/extension.ts owns the optional local-gateway client (any OpenAI-compatible /v1 endpoint) and the deterministic offline composer that answers when no gateway is reachable.
  • Python Guard Engine (engine/):
    • Operates as a standalone CLI / hook runner (gravity-validator.py).
    • Executed either via IDE pre/post tool hooks, pre-commit Git hooks, or CLI commands.
    • Communicates with the TypeScript UI via the event stream file at ~/.gemini/logs/srp_guardian_live.json.

1.4. Context-Aware Prompt Enhancement

  • The Prompt Enhancer must NEVER assume all user prompts are implementation tasks. The intent is decided in the extension, before the request leaves the machine, and the model is told which mode it is in. The three directives are mutually exclusive and only the selected one is sent:
    1. Inquiry / Consulting ("Fikirlerin nelerdir?", "Nasıl yapmalıyım?") — directive A: options with trade-offs, over-engineering risks, decision criteria. No build orders, no tool calls.
    2. Implementation / Coding ("Şunu yaz", "Modül ekle") — directive B: defensive specification (purpose, scope, module and SRP boundaries, error handling, tests).
    3. Audit / Refactoring ("Denetle", "ölü kodları incele") — directive C: no new production code, findings with dosya:satır evidence plus an impact rating, unverifiable suspicions in a separate list, refactor advice split into small revertible steps with a verification method each.
  • The user can override the classifier with a #denetle: / #danış: / #kodla: prefix; the result is reported as source: 'override'. A heuristic the user cannot contradict is a heuristic the user will stop trusting.
  • Below the high band the user is asked, not guessed. shouldAskForMode() is true unless the classification is high-confidence, and a showQuickPick presents the current guess as the default. An override is never questioned.
  • Why not ask the model? It was the previous design, and it was unverifiable: the repository could not prove compliance, so a directive was a request rather than a decision. tests/intent.test.mjs asserts that exactly one directive is emitted per prompt.
  • Signals are scored, not counted, and the ordering rule is: specific beats generic. A build verb or audit verb (weight 4) outranks a question marker (1–2); a named assessment topic such as "quality / risk / correctness" (5) outranks both. Three consequences that were bugs first:
    • Turkish keywords are matched as stems with a bounded suffix allowance (tr()), because \b-anchored literals silently fail on "teknik borcu" (ç→c softening) and "ölü kodu". The potential suffix -A-bil- is excluded: "yazabiliriz" is a hypothesis, not an order.
    • A question mark is syntactic, not semantic. It says the request is a question, never which kind, so it must not outrank a real signal — and it must be counted once: ? and a trailing mı/mi/mu/mü are the same evidence, not two.
    • One word must never fire two patterns of the same mode. English "unused" appearing in both the Turkish and the English smell list doubled its weight and outranked a build order.

2. Key Component Mechanics

2.1. Rule Matrix Pipeline Architecture

The Python engine enforces checks through an ordered, high-to-low priority pipeline:

  1. G0_SECRET_LEAK (P0 - BLOCK / WARN): Diff-safe secret and credential airbag.
    • Scope Boundary: Guarantees that AI tool-calls passing through the validator cannot write exposed credentials to target files. Out-of-band OS edits (direct PowerShell scripts, external text editors) are outside hook scope.
    • Scanning Scope: Scans strictly added_text using difflib.SequenceMatcher.
    • High-Confidence Patterns (BLOCK): Private keys (BEGIN ... PRIVATE KEY), GitHub tokens (ghp_, github_pat_, etc.), Claude/Anthropic keys (sk-ant-...), OpenAI modern keys (sk-proj-...), Google/Gemini keys (AIza...), Slack tokens (xoxb-, xoxp-, etc.).
    • Suspicious Credentials (WARN): Long Bearer tokens and connection URIs with embedded passwords.
    • Placeholder Principle: Placeholder detection (example, your_api_key, dummy) is a convenience heuristic to avoid false positives, not a security boundary. The primary security boundary is strict provider pattern matching.
    • Redaction Invariant: All detected secrets are redacted (sk-ant-****...****890) before reaching logs (srp_guardian_live.json) or stdout to prevent secondary leaks.
  2. G1_SILENT_EXCEPTION (P0 - BLOCK): Diff-safe exception integrity guard. Rejects newly added empty catch/except blocks (except: pass, catch {}).
  3. G2_TEST_INTEGRITY (P0 - BLOCK / WARN): Test suite preservation. Rejects test case deletion across full files (Counter) and test disablers (.skip(), xit()). Allows pytest.mark.xfail and treats .only() as WARN.
  4. G3_COMPILER_BYPASS (P1 - WARN ONLY): Flags newly introduced linter/compiler suppression pragmas (# noqa, # type: ignore, @ts-ignore).
  5. SRP_BOUNDARY (P0 - BLOCK): Flags multi-responsibility anti-patterns (e.g., mixing raw UI widgets with HTTP/Network requests in a single file).
  6. ARCH_FILE_GROWTH (P2 - WARN ONLY):
    • Philosophy: Prevents the "creeping monolith" anti-pattern without blocking. AI agents tend to append small chunks (+40 LOC, +40 LOC) into a single 900+ LOC file, gradually turning it into an unmaintainable god-file.
    • It measures growth, not file state. Criteria, first match wins:
      1. The write crosses the monolith line (old_loc < monolithLoc <= projected_loc, default 1000).
      2. old_loc >= creepBaseLoc (default 800) and added >= creepAddedLoc (default 80).
      3. One tool call adds >= singleWriteLoc (default 180) to an existing file.
    • Creation is not accumulation. Rules 2 and 3 skip files that do not exist yet: a brand-new cohesive module is the outcome these rules ask for. Rule 1 still applies, so a file born over the monolith line is reported.
    • Escape hatch: srp: allow-monolith (or the rest of the marker vocabulary the SRP rule already honours) silences rules 2 and 3. It deliberately does not silence rule 1.
    • Exemptions: Test files (test_*.py, *.test.ts) and configured cohesive modules are exempt.
    • Measured justification (6,404 real production file-writes, 11 repositories): 60 writes land on files already past 1,000 lines and only 30 of them carry a material addition, so the previous projected_loc >= 1000 state check repeated itself on half its fires; 359 of 1,636 real file creations added 180+ lines, so warning on creation penalised modularisation. Method and full numbers in the CHANGELOG.
  7. TEST_EVIDENCE (Phase 2 - WARN ONLY):
    • Philosophy: Not a test coverage or mutation testing tool. Acts as a lightweight pre-tool "evidence airbag" to catch AI agents changing production behavior without test updates or writing empty assertionless stubs.
    • Zero BLOCK Invariant: Under no circumstance does T1, T2, or T3 block a tool call. All decisions return decision: allow with informative warnings.
    • T1_MISSING_RELATED_TEST: Triggers when production code changes without a candidate test file on disk or when candidate test file was untouched in the active session window. Exemption allowlist (types, constants, index, *.d.ts, migrations, config, schemas) prevents false alarms.
    • T2_NO_OBSERVABLE_ASSERTION: Triggers when newly added test cases (def test_..., it(...), test(...)) contain no observable assertion pattern (assert, self.assert*, pytest.raises, expect(), .toBe(), .toEqual(), .toThrow(), etc.). Fixtures (beforeEach, describe, setUp) are exempt. Regex specifically avoids false matching on test titles starting with "should".
    • T3_SYMBOL_TO_TEST_LINK: Triggers when top-level/exported functions or classes added to production code do not appear by name in the candidate test file. Only runs when candidate test file exists (avoids warning spam when T1 already triggered).
  8. STATIC_LINTER_DIAGNOSTIC (Tier 2/3 Bridge - WARN ONLY):
    • Philosophy: The pre-tool fast path must stay < 10ms. Running eslint, ruff, or tsc synchronously inside pre-tool interceptors paralyzes the AI agent with 1–3s latency spikes.
    • Asynchronous Runner (engine/async_runner.py): Runs post-tool in the background on changed files only (Ruff for Python, ESLint for TS/JS, Godot for GDScript, and debounced tsc --noEmit across TS projects).
    • State Bridge (.gravityguard/runtime/diagnostics.json): Background tools output findings into this JSON file with timestamps.
    • Next-Hook Injection: On the next tool-call, gravity-validator.py reads diagnostics.json in < 0.5ms, checks freshness (<600s and file not modified after diagnostic), and injects actionable warnings into the AI prompt.

2.2. Event Stream Protocol (srp_guardian_live.json)

  • The Python engine records all evaluations to ~/.gemini/logs/srp_guardian_live.json.
  • Structure:
    {
      "activeGuard": "GravityGuard",
      "status": "ONLINE",
      "lastCheck": "2026-09-20T14:55:00.000Z",
      "events": [
        {
          "status": "BLOCKED" | "APPROVED",
          "timestamp": "HH:MM:SS",
          "target": "path/to/file.py",
          "action": "CREATE_FILE" | "EDIT_FILE",
          "reason": "Human-readable explanation"
        }
      ]
    }
  • The TypeScript webview watches this file using both fs.watch and a 1.5s fallback polling heartbeat.

2.3. Prompt Enhancement Pipeline (optional gateway, deterministic floor)

  • Stage 1 — local intent classification (src/intent.ts): no network, <1ms, three mutually exclusive directives (§1.4).
  • Stage 2 — gateway cascade (optional): http://<gravityguard.routerHost>:<routerPort>/v1/chat/completions with "stream": false, against any OpenAI-compatible endpoint (9Router, Ollama, LM Studio, llama.cpp). Model ids come from gravityguard.models (env: ROUTER_MODELS); default cascade is ag/gemini-3.8-flash-low → ag/gemini-3.7-flash-medium → all. Per-model timeout: gravityguard.routerTimeoutMs (default 12,000 ms).
  • Two failure classes, two behaviours. Model-level failures (HTTP error, empty content, timeout) fall through to the next model. Connection-level failures (ECONNREFUSED, ENOTFOUND, EHOSTUNREACH, ENETUNREACH, EAI_AGAIN, ECONNRESET, EPIPE) mean no model behind that host can answer, so the remaining candidates are skipped instead of each burning a full timeout.
  • Stage 3 — offline composer (always available): buildOfflinePrompt() renders a structured, mode-correct brief from the classified intent — the original request verbatim, the expected output for that mode, and the standing constraints. The user is told the result came from the offline path. This is the invariant, not a fallback: offline is the floor.
  • antigravityBridge.ping performs a real health check (GET /v1/models, 3s) and reports the endpoint, the model count, or the exact connection error.

2.4. Project Configuration (.gravityguard.json)

Block Consumed by Notes
testEvidence T1 / T2 / T3 (WARN) sourceRoots, testRoots, subsystemMappings (glob → test globs; default src/webview/**), exemptPatterns (fnmatch globs), deferredMode. sessionWindowSeconds kaldırıldı (mtime cezası yok).
review R1 (Stop gate) enabled (opt-in, default false); exemptPatterns (project-relative globs; defaults exclude tests/**, test_*, *.test.ts, …). Only production code suffixes are ever reviewed.
complexity ARCH_FILE_GROWTH (WARN only) monolithLoc (1000), singleWriteLoc (180), creepBaseLoc (800), creepAddedLoc (80).
(none) hooks.json in the live plugin The plugin invokes python scripts/srp-validator.py, which is why the repo's engine/gravity-validator.py is renamed there. Both entry points state this in their header; tools/sync_plugin.py keeps them equal.

Configuration is a convenience, never a security boundary. Every numeric key has a floor (monolithLoc: 3 would flag every file), non-numeric values, booleans and malformed blocks are ignored, and a bad config degrades to the defaults instead of raising — this code runs inside the pre-tool hook, where an exception means the write proceeds unguarded.

2.5. 5-Layer Obligation Governance Model (v1.3.1)

  • L0 (Agent Protocol): Compact invariant context in docs/KNOWLEDGE.md §0 and .gravityguard/CONTEXT.md.
  • L1 (PreTool Airbag): Fast deterministic gates (G0, G1, G2, G4, SRP, Growth).
  • L2 (Async Evidence & Governance State): Unified .gravityguard/runtime/governance.json tracking both test_obligations and doc_obligations.
  • L3 (Lifecycle Gate / Stop Hook): Stop hook returning {"decision": "continue", "reason": "..."} to trap the agent until obligations are resolved.
  • L4 (Git Pre-Commit Gate): tools/verify_doc_governance.py enforcing §6 same-commit discipline.
  • L5 (Learning Ledger): tools/telemetry_dashboard.py --candidates surfacing human-reviewed policy proposals.

Obligations belong to ONE conversation. A conversation sees its own pending obligations plus project-wide entries that no session owns (state written before sessions existed); another session's debt never blocks its Stop, and its Stop retries never touch another session's circuit breaker (governance._visible_pending, get_session_stop_retries). The project-wide lists and stop_retries are a union kept for the live monitor, not an enforcement source. The Stop hook has no target file, so it also checks the other declared workspace roots that already keep state (dispatcher._other_stop_roots) and never creates state in a root the conversation did not touch.

A review is process evidence, never an authorization (Self-Review with Candidate Fingerprint). When review.enabled is set (opt-in, path-based, tests excluded), a governed production write records an R1 review obligation; at Stop, evaluate_review_obligations closes it only if all hold: the agent wrote a schema-valid self-review record .gravityguard/self-review/<review_id>.json (reviewId, sourceFingerprint, findings list, notes ≥ 20 chars), its sourceFingerprint equals the obligation fingerprint, and the candidate on disk still hashes to the recorded fingerprint (not stale). A second, state-level gate — the final diff guard — runs git status --porcelain at Stop and folds in governed code changed out-of-band (a shell write the PreToolUse hook never saw); it is best-effort and fail-open. The invariant is deliberate and stated in the code: GravityGuard verifies that a review step happened, not that it was correct; the goal is catching omission by a weaker model, not malice. There is no HMAC, no external reviewer and no MCP observation: the agent and GravityGuard share one trust boundary, so cryptography would only simulate a boundary. The earlier Specialist Review V1 design is recorded in docs/adr/0001-specialist-review-v1.md (superseded).

One documentation policy, two gates. Which files owe a CHANGELOG entry is defined once, in engine/gravityguard_engine/doc_policy.py, and used by the Stop-time tracker and by tools/verify_doc_governance.py. Defaults: code under engine, src, plugin, rules, scripts, tools (and plugin/hooks.json); a project replaces the directory list with governance.docGovernedDirs and/or adds governance.docObligationPatterns (project-relative globs, **/ optional); enforcement is opt-in per project (governance.enforceDocObligations). Never add a second list to a gate: the two copies drifted once. Both gates only check that CHANGELOG.md changed, not that what it says is true.

One engine, several hosts. The engine speaks the Antigravity payload. Other hosts get a thin adapter that translates and calls the unchanged entry point; today that is plugin/claude-code/ (conversation id claude:<session_id>, warnings as context only, never as an allow, Bash not inspected, fail-open). A new host never gets its own copy of a rule.


3. Developer Guidelines

  • Run npm run build after editing src/*.ts, then npm test (14 TypeScript tests for the classifier and prompt composer).
  • Run npm run test:engine (or python -m unittest discover -s engine -p test_*.py) after editing the engine. The suite sets GRAVITYGUARD_LOG_DIR and GRAVITYGUARD_DISABLE_ASYNC=1 before importing any module, so it never writes to the live audit log and never spawns a real background worker.
  • Keep telemetry and network calls strictly opt-in and local-first.
  • tools/sync_plugin.py --check reports drift between the repo engine and the live plugin copy; the pre-commit hook syncs automatically and reports [autosync] DRIFT <file> -> <bytes> (dogrulandi).

4. How A New Heuristic Gets Shipped (lessons that are already paid for)

  1. Measure before you tune. A heuristic ships with its measured fire rate on the real corpus (11 repositories, last 400 commits, 6,404 production file-writes) and at least one true-positive and one false-positive example from that history. A number nobody measured is a guess.

  2. Test behaviour, not the predicate. The first cut of the srp: allow-monolith exemption reused is_ts_cohesive_monolith() — which answers a different question and is true for nearly every TypeScript file — and the mistake was caught by an existing behaviour test going silent, not by a unit test of the predicate. Assertions must exercise the rule through the hook.

  3. Claim parity with an A/B, not an absolute. Latency comparisons run interleaved against git HEAD in the same time slice. An absolute "0.11 ms" is host- and load-dependent; the ratio is the claim that survives. A first implementation regressed 1.35x purely by copying a dict per call — invisible without the A/B.

  4. One escape-hatch vocabulary. A new marker or config key must reuse an existing one (srp: allow-monolith) or justify why it cannot.

  5. WARN rules may accept a human exemption; BLOCK rules may not. Extending complexity or the marker to a blocking rule re-opens Anti-Goal 5 and must be argued explicitly.

  6. Schema is a contract. The hook stdout permits only decision, reason, permissionOverrides, overwrite; protojson discards the entire response for an unknown field, and a test harness using plain json.loads cannot observe that class of bug. run_validator() gates every response against the allowed key set for this reason.

  7. "Could not register service worker: InvalidStateError" is a host failure, not an extension bug. The error is raised by VS Code's own webview host page (contrib/webview/browser/pre/index.html) before the extension's webview.html is delivered, so no html-assignment timing can prevent it (upstream: microsoft/vscode#125993). Measured on a clone of the real IDE profile (same extensions, no caches): 8/8 full restarts and 12/12 reloads clean, and the clone re-created its own Service Worker store. The broken case was a long-running IDE process whose Service Worker folder had been deleted underneath it by tools/auto_update_ide.py: that process never recreates the store, so every new webview fails until the IDE is fully quit and reopened (a window reload is not enough). Rule: never delete Service Worker / Code Cache while an Antigravity process runs (the updater now refuses), and tell the user to fully restart when recovery gives up.


5. Release Cutting Ritual (repeatable — proven on v1.3.0)

5.0 When to cut a release (settled policy, not an open question)

  • Changes accumulate on main under CHANGELOG.md → [Unreleased].
  • Cut a release when there is a coherent set worth shipping AND at least one user-visible behaviour change. Every behaviour change gets its own Upgrade Notes entry.
  • Bug fixes and debt closures do not require a release of their own.
  • Escalation rule: if [Unreleased] passes ~10 commits or a month, cut a release even without a behaviour change. Accumulation is a default, not a licence to let the section rot.
  • The alternative (a version per behaviour change) costs a packaging and verification cycle per change most users never see. Accumulation pays that cost in a longer notes section — and the notes are the deliverable that makes a release trustworthy, not work to be avoided.

5.1 The cut itself, with the command that proves each step

A release is not "bump the version". In order, with the command that proves each step:

  1. npm test and npm run test:engine — both green. If a perf test fails, check whether the machine is loaded before touching any bound: on this host the core evaluator measures 0.10 ms idle and 2.3 ms under three competing builds, and a random failure is not a regression.
  2. python tools/sync_plugin.py --check — the live plugin must already match the repo engine.
  3. vsce package --no-git-tag-version, then npm run verify:package. This is the gate: it compares the packaged engine's md5 against the repo and rejects forbidden content by allowlist. On the 1.3.0 cut it immediately failed — the artifact was 15 files / 80.33 KB instead of 10 / 54.69 KB, because .vscodeignore had never been touched since tests/ and docs/ came into existence.
  4. Update package.json version, README.md header + badges, and the ROADMAP status table in the same commit.
  5. CHANGELOG.md: [Unreleased] → ## [x.y.z] - <date>, with a separate Upgrade Notes block for anything a user would notice. The block is not optional: two of 1.3.0's changes altered what warnings appear, and a project with its own thresholds in .gravityguard.json is affected by them.
  6. Commit, tag, push, then gh release create v1.3.0 gravityguard-1.3.0.vsix --title "v1.3.0" --notes-file notes.md --verify-tag.
  7. Download the released asset back and compare its md5 with the local build. "The upload succeeded" is not the same claim as "the artifact on the release page is the one this tree produced".

gh is installed user-scope via winget (winget install --id GitHub.cli -e --scope user) — no admin required — and is authenticated to halilogia with repo + workflow.


6. Documentation Discipline

The doc set is six files, and they fail in different ways, so each has a rule:

File What it is for Update rule
CHANGELOG.md What happened, with the measurement and the reasoning Every behavioural change. If a rule's semantics change, the old meaning must be stated, not just the new one.
ROADMAP.md Unfinished work only A completed item moves to the CHANGELOG, not a checkbox.
ARCHITECTURE.md How the system is wired right now Any new module, config key, or pipeline stage.
docs/KNOWLEDGE.md Invariants, mechanics, and how work gets done here Any new standing procedure or lesson (§4, §5, §6).
README.md What a user sees and installs Version header, rule matrix rows, feature descriptions.
.vscodeignore What the artifact contains Any new top-level directory. Verified by npm run verify:package, not by reading.

Same-commit rule: a change that alters a rule's behaviour updates CHANGELOG + ARCHITECTURE/KNOWLEDGE in the same commit that changes the code. A rule documented one release later is a rule someone will have already worked around.

brain/knowledge.md is an untracked mirror of docs/KNOWLEDGE.md; when the tracked copy changes, copy it over (it is not version-controlled, so git will not remind anyone).