sf snapshot: pre-dispatch, uncommitted changes after 47m inactivity
This commit is contained in:
parent
8e4081e6f1
commit
b43bf6991e
68 changed files with 346 additions and 384 deletions
|
|
@ -149,7 +149,7 @@ The codebase is organized into these areas. All are open to contributions:
|
|||
| MCP server | `packages/mcp-server` | Project state tools and MCP protocol |
|
||||
| SF extension | `src/resources/extensions/sf/` | SF workflow — RFC required for auto-mode |
|
||||
| Other extensions | `src/resources/extensions/` | Browser, search, voice, MCP client, etc. |
|
||||
| Native engine | `native/` | Rust N-API modules (grep, git, AST, etc.) |
|
||||
| Native engine | `rust-engine/` | Rust N-API modules (grep, git, AST, etc.) |
|
||||
| VS Code extension | `vscode-extension/` | Chat participant, sidebar, RPC integration |
|
||||
| Web interface | `web/` | Browser-based dashboard |
|
||||
| CI/Build | `.github/`, `scripts/` | Workflows, build scripts |
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
"useIgnoreFile": true
|
||||
},
|
||||
"files": {
|
||||
"includes": ["**", "!!**/dist", "!!**/dist-test", "!!**/native/npm"]
|
||||
"includes": ["**", "!!**/dist", "!!**/dist-test", "!!**/rust-engine/npm"]
|
||||
},
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ Design documents, ADRs, and internal references. Located in [`dev/`](./dev/).
|
|||
| Guide | Description |
|
||||
|-------|-------------|
|
||||
| [Architecture Overview](./dev/architecture.md) | System design, extension model, state-on-disk, and dispatch pipeline |
|
||||
| [Native Engine](../native/README.md) | Rust N-API modules for performance-critical operations |
|
||||
| [Native Engine](../rust-engine/README.md) | Rust N-API modules for performance-critical operations |
|
||||
| [ADR-001: Branchless Worktree Architecture](./dev/ADR-001-branchless-worktree-architecture.md) | Decision record for the v2.14 git architecture |
|
||||
| [ADR-003: Pipeline Simplification](./dev/ADR-003-pipeline-simplification.md) | Research merged into planning, mechanical completion (v2.30) |
|
||||
| [ADR-004: Capability-Aware Model Routing](./dev/ADR-004-capability-aware-model-routing.md) | Extend routing from tier/cost selection to task-capability matching |
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ The question the SPEC retarget didn't have to answer: **now that this much is he
|
|||
|
||||
- **sf core (TypeScript on pi-mono): unchanged.** SPEC §1 retarget rationale stands. Pi-mono SDK alignment, MCP-server story, ~200+ TS files, real production users — none of it justifies a 3–6 month rewrite.
|
||||
- **New services: Go on Charm, comprehensively.** sf-worker (ADR-013), Singularity Knowledge + Agent Platform (ADR-014), flight recorder (ADR-015), Charm TUI client (ADR-017) — all in Go using the Charm ecosystem.
|
||||
- **Native engine (Rust): permanent.** ~11k LOC in `native/` (git, text, forge_parser, grep, highlight, ast, diff, etc.) is best-of-breed and not re-implementable in Go without losing performance. Bindings (napi-rs from TS today; cgo from Go for new services if needed) flex per consumer.
|
||||
- **Native engine (Rust): permanent.** ~11k LOC in `rust-engine/` (git, text, forge_parser, grep, highlight, ast, diff, etc.) is best-of-breed and not re-implementable in Go without losing performance. Bindings (napi-rs from TS today; cgo from Go for new services if needed) flex per consumer.
|
||||
- **Pony adoption: now, not deferred.** Reversed from initial conservative stance. Adopting pony from day one in Phase-3 admin surfaces (Singularity Memory admin UI, future audit dashboards) — admin tolerates churn better than user-facing surfaces, and the foundation bet pays back if pony stabilises.
|
||||
- **Other `charmbracelet/x/*` packages: adopted comprehensively.** When a new Go service needs a primitive (image rendering, session recording, pty, editor, input handling), use the `x/*` package. Don't reinvent.
|
||||
- **Re-evaluation trigger: 12 months from first Go service in production.** If >50% of *new* sf code lands in Go services, the question of consolidating sf core becomes worth re-asking. Until then, polyglot is the right cost shape.
|
||||
|
|
|
|||
|
|
@ -847,55 +847,55 @@
|
|||
|
||||
---
|
||||
|
||||
## native/ — Rust Engine
|
||||
## rust-engine/ — Rust Engine
|
||||
|
||||
| File | System Label(s) | Description |
|
||||
|------|-----------------|-------------|
|
||||
| native/crates/engine/src/lib.rs | Native/Rust Tools | N-API entry point exposing all Rust modules |
|
||||
| native/crates/engine/src/grep.rs | File Search, Native/Rust Tools | Ripgrep-backed regex search with context/globbing |
|
||||
| native/crates/engine/src/glob.rs | File Search, Native/Rust Tools | Glob-pattern FS discovery with gitignore + scan cache |
|
||||
| native/crates/engine/src/fd.rs | File Search, Native/Rust Tools | Fuzzy file discovery for autocomplete/@-mentions |
|
||||
| native/crates/engine/src/highlight.rs | Syntax Highlighting, Native/Rust Tools | Syntect-backed ANSI syntax highlighting |
|
||||
| native/crates/engine/src/ast.rs | AST, Native/Rust Tools | Linker shim for AST N-API registrations |
|
||||
| native/crates/engine/src/diff.rs | Text Processing, Native/Rust Tools | Fuzzy matching, Unicode normalization, unified diffs |
|
||||
| native/crates/engine/src/image.rs | Image Processing, Native/Rust Tools | Image decode/encode and resize |
|
||||
| native/crates/engine/src/html.rs | Text Processing, Native/Rust Tools | HTML to Markdown conversion |
|
||||
| native/crates/engine/src/text.rs | Text Processing, Native/Rust Tools | ANSI-aware text measurement and slicing |
|
||||
| native/crates/engine/src/truncate.rs | Text Processing, Native/Rust Tools | Line-boundary-aware output truncation |
|
||||
| native/crates/engine/src/ps.rs | Native/Rust Tools | Cross-platform process tree management |
|
||||
| native/crates/engine/src/clipboard.rs | Native/Rust Tools | Clipboard read/write for text and images |
|
||||
| native/crates/engine/src/json_parse.rs | Text Processing, Native/Rust Tools | Streaming JSON parser with partial recovery |
|
||||
| native/crates/engine/src/sf_parser.rs | SF Workflow, Native/Rust Tools | .sf/ directory file parser (markdown, frontmatter) |
|
||||
| native/crates/engine/src/ttsr.rs | TTSR, Native/Rust Tools | TTSR regex engine with compiled RegexSet |
|
||||
| native/crates/engine/src/stream_process.rs | Text Processing, Native/Rust Tools | Bash stream processor (UTF-8, ANSI strip, binary) |
|
||||
| native/crates/engine/src/xxhash.rs | Native/Rust Tools | xxHash32 for hashline edit tool |
|
||||
| native/crates/engine/src/git.rs | Native/Rust Tools | Native git operations via libgit2 |
|
||||
| native/crates/engine/src/fs_cache.rs | File Search, Native/Rust Tools | TTL-based FS scan cache with explicit invalidation |
|
||||
| native/crates/engine/src/glob_util.rs | File Search, Native/Rust Tools | Shared glob-pattern helpers |
|
||||
| native/crates/engine/src/task.rs | Native/Rust Tools | Blocking work on libuv thread pool with cancellation |
|
||||
| native/crates/engine/build.rs | Build System | Cargo build script for napi-build compilation |
|
||||
| native/crates/grep/src/lib.rs | File Search, Native/Rust Tools | Ripgrep search library (in-memory and on-disk) |
|
||||
| native/crates/ast/src/lib.rs | AST, Native/Rust Tools | AST-aware structural search and rewrite engine |
|
||||
| native/crates/ast/src/ast.rs | AST, Native/Rust Tools | ast-grep integration for structural code search |
|
||||
| native/crates/ast/src/language/mod.rs | AST, Native/Rust Tools | Vendored language defs and tree-sitter bindings |
|
||||
| native/crates/ast/src/language/parsers.rs | AST, Native/Rust Tools | Pre-compiled tree-sitter parsers (50+ languages) |
|
||||
| rust-engine/crates/engine/src/lib.rs | Native/Rust Tools | N-API entry point exposing all Rust modules |
|
||||
| rust-engine/crates/engine/src/grep.rs | File Search, Native/Rust Tools | Ripgrep-backed regex search with context/globbing |
|
||||
| rust-engine/crates/engine/src/glob.rs | File Search, Native/Rust Tools | Glob-pattern FS discovery with gitignore + scan cache |
|
||||
| rust-engine/crates/engine/src/fd.rs | File Search, Native/Rust Tools | Fuzzy file discovery for autocomplete/@-mentions |
|
||||
| rust-engine/crates/engine/src/highlight.rs | Syntax Highlighting, Native/Rust Tools | Syntect-backed ANSI syntax highlighting |
|
||||
| rust-engine/crates/engine/src/ast.rs | AST, Native/Rust Tools | Linker shim for AST N-API registrations |
|
||||
| rust-engine/crates/engine/src/diff.rs | Text Processing, Native/Rust Tools | Fuzzy matching, Unicode normalization, unified diffs |
|
||||
| rust-engine/crates/engine/src/image.rs | Image Processing, Native/Rust Tools | Image decode/encode and resize |
|
||||
| rust-engine/crates/engine/src/html.rs | Text Processing, Native/Rust Tools | HTML to Markdown conversion |
|
||||
| rust-engine/crates/engine/src/text.rs | Text Processing, Native/Rust Tools | ANSI-aware text measurement and slicing |
|
||||
| rust-engine/crates/engine/src/truncate.rs | Text Processing, Native/Rust Tools | Line-boundary-aware output truncation |
|
||||
| rust-engine/crates/engine/src/ps.rs | Native/Rust Tools | Cross-platform process tree management |
|
||||
| rust-engine/crates/engine/src/clipboard.rs | Native/Rust Tools | Clipboard read/write for text and images |
|
||||
| rust-engine/crates/engine/src/json_parse.rs | Text Processing, Native/Rust Tools | Streaming JSON parser with partial recovery |
|
||||
| rust-engine/crates/engine/src/sf_parser.rs | SF Workflow, Native/Rust Tools | .sf/ directory file parser (markdown, frontmatter) |
|
||||
| rust-engine/crates/engine/src/ttsr.rs | TTSR, Native/Rust Tools | TTSR regex engine with compiled RegexSet |
|
||||
| rust-engine/crates/engine/src/stream_process.rs | Text Processing, Native/Rust Tools | Bash stream processor (UTF-8, ANSI strip, binary) |
|
||||
| rust-engine/crates/engine/src/xxhash.rs | Native/Rust Tools | xxHash32 for hashline edit tool |
|
||||
| rust-engine/crates/engine/src/git.rs | Native/Rust Tools | Native git operations via libgit2 |
|
||||
| rust-engine/crates/engine/src/fs_cache.rs | File Search, Native/Rust Tools | TTL-based FS scan cache with explicit invalidation |
|
||||
| rust-engine/crates/engine/src/glob_util.rs | File Search, Native/Rust Tools | Shared glob-pattern helpers |
|
||||
| rust-engine/crates/engine/src/task.rs | Native/Rust Tools | Blocking work on libuv thread pool with cancellation |
|
||||
| rust-engine/crates/engine/build.rs | Build System | Cargo build script for napi-build compilation |
|
||||
| rust-engine/crates/grep/src/lib.rs | File Search, Native/Rust Tools | Ripgrep search library (in-memory and on-disk) |
|
||||
| rust-engine/crates/ast/src/lib.rs | AST, Native/Rust Tools | AST-aware structural search and rewrite engine |
|
||||
| rust-engine/crates/ast/src/ast.rs | AST, Native/Rust Tools | ast-grep integration for structural code search |
|
||||
| rust-engine/crates/ast/src/language/mod.rs | AST, Native/Rust Tools | Vendored language defs and tree-sitter bindings |
|
||||
| rust-engine/crates/ast/src/language/parsers.rs | AST, Native/Rust Tools | Pre-compiled tree-sitter parsers (50+ languages) |
|
||||
|
||||
## packages/native/src/ — Node.js Rust Bindings
|
||||
## packages/rust-engine/src/ — Node.js Rust Bindings
|
||||
|
||||
| File | System Label(s) | Description |
|
||||
|------|-----------------|-------------|
|
||||
| packages/native/src/native.ts | Native/Rust Tools, Node.js Bindings | Native addon loader with platform fallback |
|
||||
| packages/native/src/grep/index.ts | File Search, Node.js Bindings | Ripgrep wrapper for regex search |
|
||||
| packages/native/src/fd/index.ts | File Search, Node.js Bindings | Fuzzy file discovery wrapper |
|
||||
| packages/native/src/highlight/index.ts | Syntax Highlighting, Node.js Bindings | Syntax highlighting wrapper |
|
||||
| packages/native/src/image/index.ts | Image Processing, Node.js Bindings | Image processing wrapper |
|
||||
| packages/native/src/html/index.ts | Text Processing, Node.js Bindings | HTML to Markdown wrapper |
|
||||
| packages/native/src/diff/index.ts | Text Processing, Node.js Bindings | Text diffing wrapper |
|
||||
| packages/native/src/ps/index.ts | Native/Rust Tools, Node.js Bindings | Process tree management wrapper |
|
||||
| packages/native/src/truncate/index.ts | Text Processing, Node.js Bindings | Output truncation wrapper |
|
||||
| packages/native/src/json-parse/index.ts | Text Processing, Node.js Bindings | JSON parsing wrapper |
|
||||
| packages/native/src/stream-process/index.ts | Text Processing, Node.js Bindings | Stream processing wrapper |
|
||||
| packages/native/src/ttsr/index.ts | TTSR, Node.js Bindings | TTSR regex engine wrapper |
|
||||
| packages/rust-engine/src/native.ts | Native/Rust Tools, Node.js Bindings | Native addon loader with platform fallback |
|
||||
| packages/rust-engine/src/grep/index.ts | File Search, Node.js Bindings | Ripgrep wrapper for regex search |
|
||||
| packages/rust-engine/src/fd/index.ts | File Search, Node.js Bindings | Fuzzy file discovery wrapper |
|
||||
| packages/rust-engine/src/highlight/index.ts | Syntax Highlighting, Node.js Bindings | Syntax highlighting wrapper |
|
||||
| packages/rust-engine/src/image/index.ts | Image Processing, Node.js Bindings | Image processing wrapper |
|
||||
| packages/rust-engine/src/html/index.ts | Text Processing, Node.js Bindings | HTML to Markdown wrapper |
|
||||
| packages/rust-engine/src/diff/index.ts | Text Processing, Node.js Bindings | Text diffing wrapper |
|
||||
| packages/rust-engine/src/ps/index.ts | Native/Rust Tools, Node.js Bindings | Process tree management wrapper |
|
||||
| packages/rust-engine/src/truncate/index.ts | Text Processing, Node.js Bindings | Output truncation wrapper |
|
||||
| packages/rust-engine/src/json-parse/index.ts | Text Processing, Node.js Bindings | JSON parsing wrapper |
|
||||
| packages/rust-engine/src/stream-process/index.ts | Text Processing, Node.js Bindings | Stream processing wrapper |
|
||||
| packages/rust-engine/src/ttsr/index.ts | TTSR, Node.js Bindings | TTSR regex engine wrapper |
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -964,13 +964,13 @@ Quick lookup: which files are part of each system?
|
|||
| **Agent Core** | pi-agent-core/src/*, pi-coding-agent/src/core/agent-session.ts, agent-loop.ts, agent.ts, event-bus.ts, sdk.ts |
|
||||
| **AI Providers** | pi-ai/src/providers/*, pi-ai/src/stream.ts, pi-ai/src/models*.ts |
|
||||
| **API Routes** | web/app/api/**/*.ts |
|
||||
| **AST** | native/crates/ast/*, packages/native/src/ast/ |
|
||||
| **AST** | rust-engine/crates/ast/*, packages/rust-engine/src/ast/ |
|
||||
| **Async Jobs** | src/resources/extensions/async-jobs/* |
|
||||
| **Auth / OAuth** | pi-ai/src/utils/oauth/*, src/web/web-auth-storage.ts, core/auth-storage.ts, src/pi-migration.ts, aws-auth/index.ts, web/lib/auth.ts |
|
||||
| **Auto Engine** | src/resources/extensions/sf/auto*.ts, sf/auto-loop.ts, sf/auto-supervisor.ts, sf/unit-runtime.ts |
|
||||
| **Bg Shell** | src/resources/extensions/bg-shell/* |
|
||||
| **Browser Tools** | src/resources/extensions/browser-tools/* |
|
||||
| **Build System** | scripts/*, native/crates/engine/build.rs |
|
||||
| **Build System** | scripts/*, rust-engine/crates/engine/build.rs |
|
||||
| **CLI** | src/cli.ts, src/cli-web-branch.ts, src/help-text.ts, src/update*.ts, pi-coding-agent/src/cli.ts, src/worktree-cli.ts |
|
||||
| **CMux** | src/resources/extensions/cmux/index.ts |
|
||||
| **Commands** | sf/commands*.ts, sf/exit-command.ts, sf/undo.ts, sf/kill.ts, pi-coding-agent/src/core/slash-commands.ts |
|
||||
|
|
@ -981,11 +981,11 @@ Quick lookup: which files are part of each system?
|
|||
| **Event System** | pi-coding-agent/src/core/event-bus.ts, sf/auto-observability.ts |
|
||||
| **Extension Registry** | src/extension-discovery.ts, src/extension-registry.ts, src/bundled-extension-paths.ts |
|
||||
| **Extensions** | pi-coding-agent/src/core/extensions/*, src/resource-loader.ts |
|
||||
| **File Search** | native/crates/engine/src/grep.rs, glob.rs, fd.rs, fs_cache.rs, packages/native/src/grep/*, fd/*, core/tools/grep.ts, find.ts |
|
||||
| **File Search** | rust-engine/crates/engine/src/grep.rs, glob.rs, fd.rs, fs_cache.rs, packages/rust-engine/src/grep/*, fd/*, core/tools/grep.ts, find.ts |
|
||||
| **SF Workflow** | src/resources/extensions/sf/* (non-auto), sf/reports.ts, sf/notifications.ts, sf/prompts/*, sf/workflow-templates/* |
|
||||
| **Google Search** | src/resources/extensions/google-search/index.ts |
|
||||
| **Headless Mode** | src/headless*.ts |
|
||||
| **Image Processing** | native/crates/engine/src/image.rs, packages/native/src/image/*, utils/image-*.ts, web/lib/image-utils.ts |
|
||||
| **Image Processing** | rust-engine/crates/engine/src/image.rs, packages/rust-engine/src/image/*, utils/image-*.ts, web/lib/image-utils.ts |
|
||||
| **Integration Tests** | tests/**/* |
|
||||
| **Loader / Bootstrap** | src/loader.ts, src/resource-loader.ts, src/tool-bootstrap.ts, src/bundled-resource-path.ts, sf/bootstrap/* |
|
||||
| **LSP** | pi-coding-agent/src/core/lsp/* |
|
||||
|
|
@ -995,8 +995,8 @@ Quick lookup: which files are part of each system?
|
|||
| **Migration** | sf/migrate/*, src/pi-migration.ts, pi-coding-agent/src/migrations.ts, scripts/recover-*.sh |
|
||||
| **Modes** | pi-coding-agent/src/modes/* |
|
||||
| **Model System** | pi-coding-agent/src/core/model-*.ts, pi-ai/src/models*.ts, pi-ai/src/api-registry.ts, sf/model-router.ts |
|
||||
| **Native / Rust Tools** | native/crates/engine/src/* |
|
||||
| **Node.js Bindings** | packages/native/src/* |
|
||||
| **Native / Rust Tools** | rust-engine/crates/engine/src/* |
|
||||
| **Node.js Bindings** | packages/rust-engine/src/* |
|
||||
| **Onboarding** | src/onboarding.ts, src/wizard.ts, web/components/sf/onboarding/*, web/app/api/onboarding/* |
|
||||
| **Permissions** | core/extensions/project-trust.ts, core/auth-storage.ts |
|
||||
| **Remote Questions** | src/resources/extensions/remote-questions/* |
|
||||
|
|
@ -1007,10 +1007,10 @@ Quick lookup: which files are part of each system?
|
|||
| **State Machine** | sf/state.ts, sf/history.ts, sf/json-persistence.ts, sf/memory-store.ts, sf/reactive-graph.ts, core/agent-session.ts, web/lib/sf-workspace-store.tsx |
|
||||
| **Studio App** | studio/* |
|
||||
| **Subagent** | src/resources/extensions/subagent/*, src/resources/agents/* |
|
||||
| **Syntax Highlighting** | native/crates/engine/src/highlight.rs, packages/native/src/highlight/* |
|
||||
| **Text Processing** | native/crates/engine/src/diff.rs, html.rs, text.rs, truncate.rs, json_parse.rs, stream_process.rs |
|
||||
| **Syntax Highlighting** | rust-engine/crates/engine/src/highlight.rs, packages/rust-engine/src/highlight/* |
|
||||
| **Text Processing** | rust-engine/crates/engine/src/diff.rs, html.rs, text.rs, truncate.rs, json_parse.rs, stream_process.rs |
|
||||
| **Tool System** | pi-coding-agent/src/core/tools/*, core/bash-executor.ts, core/exec.ts |
|
||||
| **TTSR** | src/resources/extensions/ttsr/*, native/crates/engine/src/ttsr.rs, packages/native/src/ttsr/* |
|
||||
| **TTSR** | src/resources/extensions/ttsr/*, rust-engine/crates/engine/src/ttsr.rs, packages/rust-engine/src/ttsr/* |
|
||||
| **TUI Components** | packages/pi-tui/src/*, pi-coding-agent/src/modes/interactive/components/*, pi-coding-agent/src/modes/interactive/controllers/* |
|
||||
| **Universal Config** | src/resources/extensions/universal-config/* |
|
||||
| **Voice** | src/resources/extensions/voice/* |
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ The `-dev.` prerelease identifier is distinct from the existing `-next.` convent
|
|||
|
||||
Dev versions (`@dev` tag) use the native binaries from the most recent stable `build-native.yml` release. The `optionalDependencies` in `package.json` use `>=` ranges, so a `-dev.` version of `sf-run` resolves the latest stable `@sf-build/engine-*` packages from the registry.
|
||||
|
||||
If a PR modifies Rust native crate code (`native/` directory), the dev publish will bundle stale native binaries. This is acceptable because:
|
||||
If a PR modifies Rust native crate code (`rust-engine/` directory), the dev publish will bundle stale native binaries. This is acceptable because:
|
||||
- Native crate changes are infrequent and always accompanied by a `v*` tag release
|
||||
- The Test stage validates the installed package works end-to-end
|
||||
- Full native binary validation happens via `build-native.yml` on the version tag
|
||||
|
|
|
|||
10
package.json
10
package.json
|
|
@ -62,7 +62,7 @@
|
|||
"test:packages": "node --test packages/pi-coding-agent/dist/core/*.test.js packages/pi-coding-agent/dist/core/tools/spawn-shell-windows.test.js",
|
||||
"test:marketplace": "node scripts/with-env.mjs SF_TEST_CLONE_MARKETPLACES=1 -- node --import ./src/resources/extensions/sf/tests/resolve-ts.mjs --experimental-strip-types --test src/resources/extensions/sf/tests/claude-import-tui.test.ts src/resources/extensions/sf/tests/plugin-importer-live.test.ts src/tests/marketplace-discovery.test.ts",
|
||||
"test:sf-light": "node --max-old-space-size=2048 --import ./src/resources/extensions/sf/tests/resolve-ts.mjs --experimental-strip-types --test-timeout=30000 --test \"src/resources/extensions/sf/tests/*.test.ts\"",
|
||||
"test:coverage": "c8 --reporter=text --reporter=lcov --exclude=\"src/resources/extensions/sf/tests/**\" --exclude=\"src/tests/**\" --exclude=\"scripts/**\" --exclude=\"native/**\" --exclude=\"node_modules/**\" --check-coverage --statements=40 --lines=40 --branches=20 --functions=20 node --import ./src/resources/extensions/sf/tests/resolve-ts.mjs --experimental-strip-types --experimental-test-isolation=process --test src/resources/extensions/sf/tests/*.test.ts src/resources/extensions/sf/tests/*.test.mjs src/tests/*.test.ts src/resources/extensions/shared/tests/*.test.ts",
|
||||
"test:coverage": "c8 --reporter=text --reporter=lcov --exclude=\"src/resources/extensions/sf/tests/**\" --exclude=\"src/tests/**\" --exclude=\"scripts/**\" --exclude=\"rust-engine/**\" --exclude=\"node_modules/**\" --check-coverage --statements=40 --lines=40 --branches=20 --functions=20 node --import ./src/resources/extensions/sf/tests/resolve-ts.mjs --experimental-strip-types --experimental-test-isolation=process --test src/resources/extensions/sf/tests/*.test.ts src/resources/extensions/sf/tests/*.test.mjs src/tests/*.test.ts src/resources/extensions/shared/tests/*.test.ts",
|
||||
"test:integration": "node --import ./src/resources/extensions/sf/tests/resolve-ts.mjs --experimental-strip-types --test \"src/tests/integration/*.test.ts\" \"src/resources/extensions/sf/tests/integration/*.test.ts\" \"src/resources/extensions/async-jobs/*.test.ts\" \"src/resources/extensions/browser-tools/tests/*.test.mjs\"",
|
||||
"pretest": "npm run typecheck:extensions",
|
||||
"test": "npm run test:unit && npm run test:integration",
|
||||
|
|
@ -71,12 +71,12 @@
|
|||
"test:fixtures:record": "node scripts/with-env.mjs SF_FIXTURE_MODE=record -- node --experimental-strip-types tests/fixtures/record.ts",
|
||||
"test:live": "node scripts/with-env.mjs SF_LIVE_TESTS=1 -- node --experimental-strip-types tests/live/run.ts",
|
||||
"test:browser-tools": "node --test src/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs src/resources/extensions/browser-tools/tests/browser-tools-integration.test.mjs",
|
||||
"test:native": "node --test packages/native/src/__tests__/grep.test.mjs",
|
||||
"test:native": "node --test packages/rust-engine/src/__tests__/grep.test.mjs",
|
||||
"test:secret-scan": "node --import ./src/resources/extensions/sf/tests/resolve-ts.mjs --experimental-strip-types --test src/tests/secret-scan.test.ts",
|
||||
"secret-scan": "node scripts/secret-scan.mjs",
|
||||
"secret-scan:install-hook": "node scripts/install-hooks.mjs",
|
||||
"build:native": "node native/scripts/build.js",
|
||||
"build:native:dev": "node native/scripts/build.js --dev",
|
||||
"build:native": "node rust-engine/scripts/build.js",
|
||||
"build:native:dev": "node rust-engine/scripts/build.js --dev",
|
||||
"dev": "node scripts/dev.js",
|
||||
"sf": "node scripts/dev-cli.js",
|
||||
"sf:web": "npm run build:pi && npm run copy-resources && node scripts/build-web-if-stale.cjs && node scripts/dev-cli.js --web",
|
||||
|
|
@ -86,7 +86,7 @@
|
|||
"pi:install-global": "node scripts/install-pi-global.js",
|
||||
"pi:uninstall-global": "node scripts/uninstall-pi-global.js",
|
||||
"sync-pkg-version": "node scripts/sync-pkg-version.cjs",
|
||||
"sync-platform-versions": "node native/scripts/sync-platform-versions.cjs",
|
||||
"sync-platform-versions": "node rust-engine/scripts/sync-platform-versions.cjs",
|
||||
"validate-pack": "node scripts/validate-pack.js",
|
||||
"typecheck:extensions": "npm run check:versioned-json && tsc --noEmit --project tsconfig.extensions.json",
|
||||
"check:versioned-json": "node scripts/check-versioned-json.mjs",
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@
|
|||
"types": "./dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc -p tsconfig.json",
|
||||
"build:native": "node ../../native/scripts/build.js",
|
||||
"build:native:dev": "node ../../native/scripts/build.js --dev",
|
||||
"build:native": "node ../../rust-engine/scripts/build.js",
|
||||
"build:native:dev": "node ../../rust-engine/scripts/build.js --dev",
|
||||
"test": "npm run build:native:dev && node --test src/__tests__/grep.test.mjs src/__tests__/ps.test.mjs src/__tests__/glob.test.mjs src/__tests__/clipboard.test.mjs src/__tests__/highlight.test.mjs src/__tests__/html.test.mjs src/__tests__/text.test.mjs src/__tests__/fd.test.mjs src/__tests__/image.test.mjs"
|
||||
},
|
||||
"exports": {
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@
|
|||
* Locates and loads the compiled Rust N-API addon (`.node` file).
|
||||
* Resolution order:
|
||||
* 1. @singularity-forge/engine-{platform} npm optional dependency (production install)
|
||||
* 2. native/addon/forge_engine.{platform}.node (local release build)
|
||||
* 3. native/addon/forge_engine.dev.node (local debug build)
|
||||
* 2. rust-engine/addon/forge_engine.{platform}.node (local release build)
|
||||
* 3. rust-engine/addon/forge_engine.dev.node (local debug build)
|
||||
*/
|
||||
|
||||
import * as path from "node:path";
|
||||
|
|
@ -16,7 +16,7 @@ import * as path from "node:path";
|
|||
const _dirname = __dirname;
|
||||
const _require = require;
|
||||
|
||||
const addonDir = path.resolve(_dirname, "..", "..", "..", "native", "addon");
|
||||
const addonDir = path.resolve(_dirname, "..", "..", "..", "rust-engine", "addon");
|
||||
const platformTag = `${process.platform}-${process.arch}`;
|
||||
|
||||
/** Map Node.js platform/arch to the npm package suffix */
|
||||
|
|
@ -44,7 +44,7 @@ function loadNative(): Record<string, unknown> {
|
|||
}
|
||||
}
|
||||
|
||||
// 2. Try local release build (native/addon/forge_engine.{platform}.node)
|
||||
// 2. Try local release build (rust-engine/addon/forge_engine.{platform}.node)
|
||||
const releasePath = path.join(addonDir, `forge_engine.${platformTag}.node`);
|
||||
try {
|
||||
_loadedSuccessfully = true; return _require(releasePath) as Record<string, unknown>;
|
||||
|
|
@ -53,7 +53,7 @@ function loadNative(): Record<string, unknown> {
|
|||
errors.push(`${releasePath}: ${message}`);
|
||||
}
|
||||
|
||||
// 3. Try local dev build (native/addon/forge_engine.dev.node)
|
||||
// 3. Try local dev build (rust-engine/addon/forge_engine.dev.node)
|
||||
const devPath = path.join(addonDir, "forge_engine.dev.node");
|
||||
try {
|
||||
_loadedSuccessfully = true; return _require(devPath) as Record<string, unknown>;
|
||||
|
|
|
|||
|
|
@ -208,7 +208,7 @@ export function parseArgs(args: string[], extensionFlags?: Map<string, Extension
|
|||
}
|
||||
|
||||
export function printHelp(): void {
|
||||
console.log(`${chalk.bold(APP_NAME)} - AI coding assistant with read, bash, edit, write tools
|
||||
console.log(`${chalk.bold(APP_NAME)} - AI coding assistant with native read/search/edit/write tools
|
||||
|
||||
${chalk.bold("Usage:")}
|
||||
${APP_NAME} [options] [@files...] [messages...]
|
||||
|
|
@ -238,7 +238,7 @@ ${chalk.bold("Options:")}
|
|||
Supports globs (anthropic/*, *sonnet*) and fuzzy matching
|
||||
--no-tools Disable all built-in tools
|
||||
--tools <tools> Comma-separated list of tools to enable (default: read,bash,edit,write)
|
||||
Available: read, bash, edit, write, lsp, grep, find, ls
|
||||
Available: read, grep, find, ls, bash, edit, write, lsp
|
||||
--thinking <level> Set thinking level: off, minimal, low, medium, high, xhigh
|
||||
--extension, -e <path> Load an extension file (can be used multiple times)
|
||||
--no-extensions, -ne Disable extension discovery (explicit -e paths still work)
|
||||
|
|
@ -339,7 +339,7 @@ ${chalk.bold("Environment Variables:")}
|
|||
PI_OFFLINE - Disable startup network operations when set to 1/true/yes
|
||||
PI_SHARE_VIEWER_URL - Base URL for /share command (default: https://pi.dev/session/)
|
||||
|
||||
${chalk.bold("Available Tools (default: read, bash, edit, write):")}
|
||||
${chalk.bold("Available Tools (default: read, grep, find, ls, bash, edit, write, lsp):")}
|
||||
read - Read file contents
|
||||
bash - Execute bash commands
|
||||
edit - Edit files with find/replace
|
||||
|
|
|
|||
|
|
@ -160,7 +160,7 @@ export interface AgentSessionConfig {
|
|||
customTools?: ToolDefinition[];
|
||||
/** Model registry for API key resolution and model discovery */
|
||||
modelRegistry: ModelRegistry;
|
||||
/** Initial active built-in tool names. Default: [read, bash, edit, write] */
|
||||
/** Initial active built-in tool names. Default: [read, grep, find, ls, bash, edit, write, lsp] */
|
||||
initialActiveToolNames?: string[];
|
||||
/** Override base tools (useful for custom runtimes). */
|
||||
baseToolsOverride?: Record<string, AgentTool>;
|
||||
|
|
@ -2233,7 +2233,7 @@ export class AgentSession {
|
|||
|
||||
const defaultActiveToolNames = this._baseToolsOverride
|
||||
? Object.keys(this._baseToolsOverride)
|
||||
: ["read", "bash", "edit", "write", "lsp"];
|
||||
: ["read", "grep", "find", "ls", "bash", "edit", "write", "lsp"];
|
||||
const baseActiveToolNames = options.activeToolNames ?? defaultActiveToolNames;
|
||||
this._refreshToolRegistry({
|
||||
activeToolNames: baseActiveToolNames,
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ export interface CreateAgentSessionOptions {
|
|||
/** Models available for cycling (Ctrl+P in interactive mode) */
|
||||
scopedModels?: Array<{ model: Model<any>; thinkingLevel?: ThinkingLevel }>;
|
||||
|
||||
/** Built-in tools to use. Default: codingTools [read, bash, edit, write] */
|
||||
/** Built-in tools to use. Default: codingTools [read, grep, find, ls, bash, edit, write, lsp] */
|
||||
tools?: Tool[];
|
||||
/** Custom tools to register (in addition to built-in tools). */
|
||||
customTools?: ToolDefinition[];
|
||||
|
|
@ -313,8 +313,8 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|||
|
||||
const editMode = settingsManager.getEditMode();
|
||||
const defaultActiveToolNames: ToolName[] = editMode === "hashline"
|
||||
? ["hashline_read", "bash", "hashline_edit", "write", "lsp"]
|
||||
: ["read", "bash", "edit", "write", "lsp"];
|
||||
? ["hashline_read", "grep", "find", "ls", "bash", "hashline_edit", "write", "lsp"]
|
||||
: ["read", "grep", "find", "ls", "bash", "edit", "write", "lsp"];
|
||||
const initialActiveToolNames: ToolName[] = options.tools
|
||||
? options.tools.map((t) => t.name).filter((n): n is ToolName => n in allTools)
|
||||
: defaultActiveToolNames;
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ const toolDescriptions: Record<string, string> = {
|
|||
export interface BuildSystemPromptOptions {
|
||||
/** Custom system prompt (replaces default). */
|
||||
customPrompt?: string;
|
||||
/** Tools to include in prompt. Default: [read, bash, edit, write] */
|
||||
/** Tools to include in prompt. Default: [read, grep, find, ls, bash, edit, write, lsp] */
|
||||
selectedTools?: string[];
|
||||
/** Optional one-line tool snippets keyed by tool name. */
|
||||
toolSnippets?: Record<string, string>;
|
||||
|
|
@ -149,7 +149,7 @@ export function buildSystemPrompt(options: BuildSystemPromptOptions = {}): strin
|
|||
|
||||
// Build tools list based on selected tools.
|
||||
// Built-ins use toolDescriptions. Custom tools can provide one-line snippets.
|
||||
const tools = selectedTools || ["read", "bash", "edit", "write"];
|
||||
const tools = selectedTools || ["read", "grep", "find", "ls", "bash", "edit", "write", "lsp"];
|
||||
const toolsList =
|
||||
tools.length > 0
|
||||
? tools
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ import { createLspTool, lspTool } from "../lsp/index.js";
|
|||
export type Tool = AgentTool<any>;
|
||||
|
||||
// Default tools for full access mode (using process.cwd())
|
||||
export const codingTools: Tool[] = [readTool, bashTool, editTool, writeTool];
|
||||
export const codingTools: Tool[] = [readTool, grepTool, findTool, lsTool, bashTool, editTool, writeTool, lspTool];
|
||||
|
||||
// Read-only tools for exploration without modification (using process.cwd())
|
||||
export const readOnlyTools: Tool[] = [readTool, grepTool, findTool, lsTool];
|
||||
|
|
@ -156,7 +156,16 @@ export const allTools = {
|
|||
};
|
||||
|
||||
// Hashline-mode coding tools — read with hash anchors, edit with hash references
|
||||
export const hashlineCodingTools: Tool[] = [hashlineReadTool, bashTool, hashlineEditTool, writeTool];
|
||||
export const hashlineCodingTools: Tool[] = [
|
||||
hashlineReadTool,
|
||||
grepTool,
|
||||
findTool,
|
||||
lsTool,
|
||||
bashTool,
|
||||
hashlineEditTool,
|
||||
writeTool,
|
||||
lspTool,
|
||||
];
|
||||
|
||||
export type ToolName = keyof typeof allTools;
|
||||
|
||||
|
|
@ -173,9 +182,13 @@ export interface ToolsOptions {
|
|||
export function createCodingTools(cwd: string, options?: ToolsOptions): Tool[] {
|
||||
return [
|
||||
createReadTool(cwd, options?.read),
|
||||
createGrepTool(cwd),
|
||||
createFindTool(cwd),
|
||||
createLsTool(cwd),
|
||||
createBashTool(cwd, options?.bash),
|
||||
createEditTool(cwd),
|
||||
createWriteTool(cwd),
|
||||
createLspTool(cwd),
|
||||
];
|
||||
}
|
||||
|
||||
|
|
@ -211,8 +224,12 @@ export function createAllTools(cwd: string, options?: ToolsOptions): Record<Tool
|
|||
export function createHashlineCodingTools(cwd: string, options?: ToolsOptions): Tool[] {
|
||||
return [
|
||||
createHashlineReadTool(cwd, options?.read),
|
||||
createGrepTool(cwd),
|
||||
createFindTool(cwd),
|
||||
createLsTool(cwd),
|
||||
createBashTool(cwd, options?.bash),
|
||||
createHashlineEditTool(cwd),
|
||||
createWriteTool(cwd),
|
||||
createLspTool(cwd),
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,197 +1,190 @@
|
|||
/**
|
||||
* Built-in theme definitions.
|
||||
*
|
||||
* Each theme is a self-contained record of color values. Variable references
|
||||
* (e.g. "accent") are resolved against the `vars` map at load time by the
|
||||
* theme engine in theme.ts.
|
||||
*
|
||||
* To add a new built-in theme, add an entry to `builtinThemes` below.
|
||||
* The default palettes follow the Singularity Engine design system:
|
||||
* Scandinavian x IBM Carbon, ember-40 accent, warm gray/stone neutrals,
|
||||
* paper/ink semantics, hard hairlines, and restrained status color.
|
||||
*/
|
||||
|
||||
// Re-use the ThemeJson type from the schema defined in theme.ts.
|
||||
// We import only the type to avoid circular runtime dependencies.
|
||||
import type { ThemeJson } from "./theme.js";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Dark theme
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const dark: ThemeJson = {
|
||||
name: "dark",
|
||||
vars: {
|
||||
cyan: "#00d7ff",
|
||||
blue: "#5f87ff",
|
||||
green: "#b5bd68",
|
||||
red: "#cc6666",
|
||||
yellow: "#e6b800",
|
||||
gray: "#808080",
|
||||
dimGray: "#666666",
|
||||
darkGray: "#505050",
|
||||
accent: "#8abeb7",
|
||||
selectedBg: "#3a3a4a",
|
||||
userMsgBg: "#343541",
|
||||
toolPendingBg: "#282832",
|
||||
toolSuccessBg: "#283228",
|
||||
toolErrorBg: "#3c2828",
|
||||
customMsgBg: "#2d2838",
|
||||
ember40: "#ff8838",
|
||||
paper: "#f7f5f1",
|
||||
paper2: "#efece6",
|
||||
stone1: "#d4cfc3",
|
||||
stone2: "#8d877a",
|
||||
stone3: "#6b6659",
|
||||
ink1: "#1a1916",
|
||||
ink2: "#2c2a26",
|
||||
ink3: "#3d3b36",
|
||||
green: "#24a148",
|
||||
red: "#da1e28",
|
||||
blue: "#4589ff",
|
||||
yellow: "#f1c21b",
|
||||
mutedBg: "#22201d",
|
||||
selectedBg: "#2c2a26",
|
||||
userMsgBg: "#1a1916",
|
||||
toolPendingBg: "#1a1916",
|
||||
toolSuccessBg: "#172018",
|
||||
toolErrorBg: "#261717",
|
||||
customMsgBg: "#22201d",
|
||||
},
|
||||
colors: {
|
||||
accent: "accent",
|
||||
border: "blue",
|
||||
borderAccent: "cyan",
|
||||
borderMuted: "darkGray",
|
||||
accent: "ember40",
|
||||
border: "stone3",
|
||||
borderAccent: "ember40",
|
||||
borderMuted: "ink3",
|
||||
success: "green",
|
||||
error: "red",
|
||||
warning: "yellow",
|
||||
muted: "gray",
|
||||
dim: "dimGray",
|
||||
text: "",
|
||||
thinkingText: "gray",
|
||||
warning: "ember40",
|
||||
muted: "stone2",
|
||||
dim: "stone3",
|
||||
text: "paper",
|
||||
thinkingText: "stone2",
|
||||
|
||||
selectedBg: "selectedBg",
|
||||
userMessageBg: "userMsgBg",
|
||||
userMessageText: "",
|
||||
userMessageText: "paper",
|
||||
customMessageBg: "customMsgBg",
|
||||
customMessageText: "",
|
||||
customMessageLabel: "#9575cd",
|
||||
customMessageText: "paper",
|
||||
customMessageLabel: "ember40",
|
||||
toolPendingBg: "toolPendingBg",
|
||||
toolSuccessBg: "toolSuccessBg",
|
||||
toolErrorBg: "toolErrorBg",
|
||||
toolTitle: "",
|
||||
toolOutput: "gray",
|
||||
toolTitle: "ember40",
|
||||
toolOutput: "stone2",
|
||||
|
||||
mdHeading: "#f0c674",
|
||||
mdLink: "#81a2be",
|
||||
mdLinkUrl: "dimGray",
|
||||
mdCode: "accent",
|
||||
mdCodeBlock: "green",
|
||||
mdCodeBlockBorder: "gray",
|
||||
mdQuote: "gray",
|
||||
mdQuoteBorder: "gray",
|
||||
mdHr: "gray",
|
||||
mdListBullet: "accent",
|
||||
mdHeading: "paper",
|
||||
mdLink: "ember40",
|
||||
mdLinkUrl: "stone2",
|
||||
mdCode: "ember40",
|
||||
mdCodeBlock: "paper2",
|
||||
mdCodeBlockBorder: "stone3",
|
||||
mdQuote: "stone1",
|
||||
mdQuoteBorder: "ember40",
|
||||
mdHr: "stone3",
|
||||
mdListBullet: "ember40",
|
||||
|
||||
toolDiffAdded: "green",
|
||||
toolDiffRemoved: "red",
|
||||
toolDiffContext: "gray",
|
||||
toolDiffContext: "stone2",
|
||||
|
||||
syntaxComment: "#6A9955",
|
||||
syntaxKeyword: "#569CD6",
|
||||
syntaxFunction: "#DCDCAA",
|
||||
syntaxVariable: "#9CDCFE",
|
||||
syntaxString: "#CE9178",
|
||||
syntaxNumber: "#B5CEA8",
|
||||
syntaxType: "#4EC9B0",
|
||||
syntaxOperator: "#D4D4D4",
|
||||
syntaxPunctuation: "#D4D4D4",
|
||||
syntaxComment: "stone2",
|
||||
syntaxKeyword: "ember40",
|
||||
syntaxFunction: "paper",
|
||||
syntaxVariable: "paper2",
|
||||
syntaxString: "#ffb27a",
|
||||
syntaxNumber: "#f1c21b",
|
||||
syntaxType: "#33b1ff",
|
||||
syntaxOperator: "stone1",
|
||||
syntaxPunctuation: "stone2",
|
||||
|
||||
thinkingOff: "darkGray",
|
||||
thinkingMinimal: "#6e6e6e",
|
||||
thinkingLow: "#5f87af",
|
||||
thinkingMedium: "#81a2be",
|
||||
thinkingHigh: "#b294bb",
|
||||
thinkingXhigh: "#d183e8",
|
||||
thinkingOff: "ink3",
|
||||
thinkingMinimal: "stone3",
|
||||
thinkingLow: "stone2",
|
||||
thinkingMedium: "ember40",
|
||||
thinkingHigh: "#ffb27a",
|
||||
thinkingXhigh: "#fff1e6",
|
||||
|
||||
bashMode: "green",
|
||||
},
|
||||
export: {
|
||||
pageBg: "#18181e",
|
||||
cardBg: "#1e1e24",
|
||||
infoBg: "#3c3728",
|
||||
pageBg: "#1a1916",
|
||||
cardBg: "#22201d",
|
||||
infoBg: "#2c2a26",
|
||||
},
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Light theme
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const light: ThemeJson = {
|
||||
name: "light",
|
||||
vars: {
|
||||
teal: "#5a8080",
|
||||
blue: "#547da7",
|
||||
green: "#588458",
|
||||
red: "#aa5555",
|
||||
yellow: "#9a7326",
|
||||
warning: "#7a5a00",
|
||||
mediumGray: "#6c6c6c",
|
||||
dimGray: "#767676",
|
||||
lightGray: "#b0b0b0",
|
||||
selectedBg: "#d0d0e0",
|
||||
userMsgBg: "#e8e8e8",
|
||||
toolPendingBg: "#e8e8f0",
|
||||
toolSuccessBg: "#e8f0e8",
|
||||
toolErrorBg: "#f0e8e8",
|
||||
customMsgBg: "#ede7f6",
|
||||
ember40: "#ff8838",
|
||||
paper: "#f7f5f1",
|
||||
paper2: "#efece6",
|
||||
paper3: "#e6e2da",
|
||||
stone1: "#d4cfc3",
|
||||
stone2: "#8d877a",
|
||||
stone3: "#6b6659",
|
||||
ink1: "#1a1916",
|
||||
ink2: "#3d3b36",
|
||||
green: "#24a148",
|
||||
red: "#da1e28",
|
||||
blue: "#4589ff",
|
||||
yellow: "#f1c21b",
|
||||
selectedBg: "#efece6",
|
||||
userMsgBg: "#ffffff",
|
||||
toolPendingBg: "#ffffff",
|
||||
toolSuccessBg: "#f0f6ef",
|
||||
toolErrorBg: "#fff1ee",
|
||||
customMsgBg: "#efece6",
|
||||
},
|
||||
colors: {
|
||||
accent: "teal",
|
||||
border: "blue",
|
||||
borderAccent: "teal",
|
||||
borderMuted: "lightGray",
|
||||
accent: "ember40",
|
||||
border: "stone3",
|
||||
borderAccent: "ember40",
|
||||
borderMuted: "stone1",
|
||||
success: "green",
|
||||
error: "red",
|
||||
warning: "warning",
|
||||
muted: "mediumGray",
|
||||
dim: "dimGray",
|
||||
text: "",
|
||||
thinkingText: "mediumGray",
|
||||
warning: "ember40",
|
||||
muted: "stone3",
|
||||
dim: "stone2",
|
||||
text: "ink1",
|
||||
thinkingText: "stone3",
|
||||
|
||||
selectedBg: "selectedBg",
|
||||
userMessageBg: "userMsgBg",
|
||||
userMessageText: "",
|
||||
userMessageText: "ink1",
|
||||
customMessageBg: "customMsgBg",
|
||||
customMessageText: "",
|
||||
customMessageLabel: "#7e57c2",
|
||||
customMessageText: "ink1",
|
||||
customMessageLabel: "ember40",
|
||||
toolPendingBg: "toolPendingBg",
|
||||
toolSuccessBg: "toolSuccessBg",
|
||||
toolErrorBg: "toolErrorBg",
|
||||
toolTitle: "",
|
||||
toolOutput: "mediumGray",
|
||||
toolTitle: "ember40",
|
||||
toolOutput: "stone3",
|
||||
|
||||
mdHeading: "yellow",
|
||||
mdLink: "blue",
|
||||
mdLinkUrl: "dimGray",
|
||||
mdCode: "teal",
|
||||
mdCodeBlock: "green",
|
||||
mdCodeBlockBorder: "mediumGray",
|
||||
mdQuote: "mediumGray",
|
||||
mdQuoteBorder: "mediumGray",
|
||||
mdHr: "mediumGray",
|
||||
mdListBullet: "green",
|
||||
mdHeading: "ink1",
|
||||
mdLink: "ember40",
|
||||
mdLinkUrl: "stone3",
|
||||
mdCode: "ember40",
|
||||
mdCodeBlock: "ink2",
|
||||
mdCodeBlockBorder: "stone1",
|
||||
mdQuote: "stone3",
|
||||
mdQuoteBorder: "ember40",
|
||||
mdHr: "stone1",
|
||||
mdListBullet: "ember40",
|
||||
|
||||
toolDiffAdded: "green",
|
||||
toolDiffRemoved: "red",
|
||||
toolDiffContext: "mediumGray",
|
||||
toolDiffContext: "stone3",
|
||||
|
||||
syntaxComment: "#008000",
|
||||
syntaxKeyword: "#0000FF",
|
||||
syntaxFunction: "#795E26",
|
||||
syntaxVariable: "#001080",
|
||||
syntaxString: "#A31515",
|
||||
syntaxNumber: "#098658",
|
||||
syntaxType: "#267F99",
|
||||
syntaxOperator: "#000000",
|
||||
syntaxPunctuation: "#000000",
|
||||
syntaxComment: "stone3",
|
||||
syntaxKeyword: "#b5500f",
|
||||
syntaxFunction: "ink1",
|
||||
syntaxVariable: "ink2",
|
||||
syntaxString: "#8a3d0a",
|
||||
syntaxNumber: "#87620a",
|
||||
syntaxType: "#3a5c8c",
|
||||
syntaxOperator: "ink2",
|
||||
syntaxPunctuation: "stone3",
|
||||
|
||||
thinkingOff: "lightGray",
|
||||
thinkingMinimal: "#767676",
|
||||
thinkingLow: "blue",
|
||||
thinkingMedium: "teal",
|
||||
thinkingHigh: "#875f87",
|
||||
thinkingXhigh: "#8b008b",
|
||||
thinkingOff: "stone1",
|
||||
thinkingMinimal: "stone2",
|
||||
thinkingLow: "stone3",
|
||||
thinkingMedium: "ember40",
|
||||
thinkingHigh: "#e56a1a",
|
||||
thinkingXhigh: "#8a3d0a",
|
||||
|
||||
bashMode: "green",
|
||||
},
|
||||
export: {
|
||||
pageBg: "#f8f8f8",
|
||||
pageBg: "#f7f5f1",
|
||||
cardBg: "#ffffff",
|
||||
infoBg: "#fffae6",
|
||||
infoBg: "#efece6",
|
||||
},
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Export
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const builtinThemes: Record<string, ThemeJson> = { dark, light };
|
||||
|
|
|
|||
0
native/.gitignore → rust-engine/.gitignore
vendored
0
native/.gitignore → rust-engine/.gitignore
vendored
0
native/Cargo.lock → rust-engine/Cargo.lock
generated
0
native/Cargo.lock → rust-engine/Cargo.lock
generated
|
|
@ -7,7 +7,7 @@ Rust N-API addon providing high-performance native modules for SF.
|
|||
```
|
||||
JS (packages/native) -> N-API -> Rust crates
|
||||
|
||||
native/crates/
|
||||
rust-engine/crates/
|
||||
├── engine/ (N-API bindings, cdylib — 20+ modules)
|
||||
├── grep/ (ripgrep internals, pure Rust lib)
|
||||
└── ast/ (ast-grep structural search)
|
||||
|
|
@ -30,7 +30,7 @@ npm run build:native
|
|||
npm run build:native:dev
|
||||
```
|
||||
|
||||
The build script compiles the Rust code and copies the `.node` shared library to `native/addon/`.
|
||||
The build script compiles the Rust code and copies the `.node` shared library to `rust-engine/addon/`.
|
||||
|
||||
## Test
|
||||
|
||||
|
|
@ -153,7 +153,7 @@ xxHash hashing. Provides fast, non-cryptographic hashing via the xxHash algorith
|
|||
|
||||
## Adding New Modules
|
||||
|
||||
1. Create a new crate in `native/crates/` (pure Rust library)
|
||||
2. Add N-API bindings in `native/crates/engine/src/`
|
||||
3. Add TypeScript wrapper in `packages/native/src/`
|
||||
1. Create a new crate in `rust-engine/crates/` (pure Rust library)
|
||||
2. Add N-API bindings in `rust-engine/crates/engine/src/`
|
||||
3. Add TypeScript wrapper in `package./rust-engine/src/`
|
||||
4. Add the crate to `engine/Cargo.toml` dependencies
|
||||
|
|
@ -59,7 +59,7 @@ for (const name of workspacePackages) {
|
|||
}
|
||||
|
||||
// 3. Sync platform package versions (reads from root package.json)
|
||||
execSync("node native/scripts/sync-platform-versions.cjs", { cwd: root, stdio: "inherit" });
|
||||
execSync("node rust-engine/scripts/sync-platform-versions.cjs", { cwd: root, stdio: "inherit" });
|
||||
|
||||
// 4. Sync pkg/package.json (reads from pi-coding-agent)
|
||||
execSync("node scripts/sync-pkg-version.cjs", { cwd: root, stdio: "inherit" });
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ for (const dir of packageDirs) {
|
|||
if (linked > 0) process.stderr.write(` Linked ${linked} workspace package${linked !== 1 ? 's' : ''}\n`)
|
||||
if (copied > 0) process.stderr.write(` Copied ${copied} workspace package${copied !== 1 ? 's' : ''} (symlinks unavailable)\n`)
|
||||
|
||||
// Platform-specific native engine packages live under native/npm/<suffix>/, not packages/.
|
||||
// Platform-specific native engine packages live under rust-engine/npm/<suffix>/, not packages/.
|
||||
// Wire them into node_modules/@singularity-forge/ so native.ts can require() them without
|
||||
// a registry install. Only link platforms where the binary (forge_engine.node) is present.
|
||||
const nativeNpmDir = join(root, 'native', 'npm')
|
||||
|
|
|
|||
|
|
@ -5,11 +5,62 @@ import type {
|
|||
} from "@singularity-forge/pi-coding-agent";
|
||||
import { truncateToWidth, visibleWidth } from "@singularity-forge/pi-tui";
|
||||
import { refreshGitStatus } from "./git.js";
|
||||
import {
|
||||
renderPowerline,
|
||||
renderPowerlineRight,
|
||||
type Segment,
|
||||
} from "./powerline.js";
|
||||
|
||||
const RESET = "\x1b[0m";
|
||||
const BOLD = "\x1b[1m";
|
||||
|
||||
const SE = {
|
||||
ember40: "#ff8838",
|
||||
gray60: "#8d877a",
|
||||
stone60: "#6b6659",
|
||||
paper: "#f7f5f1",
|
||||
success: "#24a148",
|
||||
error: "#da1e28",
|
||||
} as const;
|
||||
|
||||
type Tone = "muted" | "accent" | "text" | "success" | "warning" | "error";
|
||||
|
||||
function hexToRgb(hex: string): { r: number; g: number; b: number } {
|
||||
const cleaned = hex.replace("#", "");
|
||||
return {
|
||||
r: parseInt(cleaned.slice(0, 2), 16),
|
||||
g: parseInt(cleaned.slice(2, 4), 16),
|
||||
b: parseInt(cleaned.slice(4, 6), 16),
|
||||
};
|
||||
}
|
||||
|
||||
function ansiFg(hex: string, text: string, bold = false): string {
|
||||
const { r, g, b } = hexToRgb(hex);
|
||||
return `\x1b[${bold ? "1;" : ""}38;2;${r};${g};${b}m${text}${RESET}`;
|
||||
}
|
||||
|
||||
function toneHex(tone: Tone): string {
|
||||
switch (tone) {
|
||||
case "accent":
|
||||
case "warning":
|
||||
return SE.ember40;
|
||||
case "success":
|
||||
return SE.success;
|
||||
case "error":
|
||||
return SE.error;
|
||||
case "text":
|
||||
return SE.paper;
|
||||
default:
|
||||
return SE.gray60;
|
||||
}
|
||||
}
|
||||
|
||||
function chip(label: string, value: string, tone: Tone = "text"): string {
|
||||
return `${ansiFg(SE.gray60, `${label} `)}${ansiFg(toneHex(tone), value)}`;
|
||||
}
|
||||
|
||||
function join(parts: string[]): string {
|
||||
return parts.filter(Boolean).join(ansiFg(SE.stone60, " | "));
|
||||
}
|
||||
|
||||
function shorten(text: string, max: number): string {
|
||||
return text.length > max ? `${text.slice(0, Math.max(0, max - 3))}...` : text;
|
||||
}
|
||||
|
||||
function getSessionStats(ctx: ExtensionContext) {
|
||||
let cost = 0;
|
||||
|
|
@ -36,7 +87,7 @@ function getSessionStats(ctx: ExtensionContext) {
|
|||
}
|
||||
|
||||
export function renderFooter(
|
||||
theme: Theme,
|
||||
_theme: Theme,
|
||||
footerData: ReadonlyFooterDataProvider,
|
||||
ctx: ExtensionContext,
|
||||
width: number,
|
||||
|
|
@ -44,95 +95,57 @@ export function renderFooter(
|
|||
const git = refreshGitStatus(process.cwd());
|
||||
const { cost, cxPct } = getSessionStats(ctx);
|
||||
|
||||
const leftSegments: Segment[] = [];
|
||||
|
||||
if (git.branch) {
|
||||
const dirtyIcon = git.dirty ? "dirty" : git.untracked ? "new" : "clean";
|
||||
leftSegments.push({
|
||||
text: `repo ${git.branch}`,
|
||||
fg: "white",
|
||||
bg: git.dirty || git.untracked ? "brightBlack" : "blue",
|
||||
bold: true,
|
||||
});
|
||||
leftSegments.push({
|
||||
text: dirtyIcon,
|
||||
fg: git.dirty || git.untracked ? "black" : "white",
|
||||
bg: git.dirty || git.untracked ? "yellow" : "green",
|
||||
bold: true,
|
||||
});
|
||||
|
||||
if (git.added || git.deleted) {
|
||||
const diffText = `Δ +${git.added}/-${git.deleted}`;
|
||||
leftSegments.push({ text: diffText, fg: "white", bg: "brightBlack" });
|
||||
}
|
||||
|
||||
if (git.lastCommit) {
|
||||
const msg =
|
||||
git.lastCommit.message.length > 20
|
||||
? git.lastCommit.message.slice(0, 19) + "…"
|
||||
: git.lastCommit.message;
|
||||
leftSegments.push({
|
||||
text: `${git.lastCommit.timeAgo} · ${msg}`,
|
||||
fg: "white",
|
||||
bg: "brightBlack",
|
||||
});
|
||||
}
|
||||
const leftParts: string[] = [];
|
||||
if (git.repo) {
|
||||
leftParts.push(ansiFg(SE.ember40, git.repo, true));
|
||||
} else {
|
||||
leftSegments.push({ text: "SF", fg: "white", bg: "blue", bold: true });
|
||||
leftParts.push(`${BOLD}${ansiFg(SE.ember40, "SF")}`);
|
||||
}
|
||||
|
||||
if (git.branch) {
|
||||
leftParts.push(chip("branch", git.branch, "muted"));
|
||||
const state = git.dirty ? "dirty" : git.untracked ? "new" : "clean";
|
||||
leftParts.push(chip("state", state, state === "clean" ? "success" : "warning"));
|
||||
if (git.added || git.deleted) {
|
||||
leftParts.push(chip("diff", `+${git.added}/-${git.deleted}`, "warning"));
|
||||
}
|
||||
if (git.ahead || git.behind) {
|
||||
leftParts.push(chip("sync", `${git.ahead} ahead ${git.behind} behind`, "warning"));
|
||||
}
|
||||
if (git.lastCommit) {
|
||||
leftParts.push(chip("last", `${git.lastCommit.timeAgo} ${shorten(git.lastCommit.message, 26)}`, "muted"));
|
||||
}
|
||||
}
|
||||
|
||||
// Extension statuses
|
||||
const statuses = Array.from(footerData.getExtensionStatuses().entries())
|
||||
.sort(([a], [b]) => a.localeCompare(b))
|
||||
.map(([, text]) => text.trim())
|
||||
.filter(Boolean);
|
||||
if (statuses.length) {
|
||||
leftSegments.push({
|
||||
text: `status ${statuses.join(" ")}`,
|
||||
fg: "white",
|
||||
bg: "brightBlack",
|
||||
});
|
||||
leftParts.push(chip("status", statuses.join(" "), "accent"));
|
||||
}
|
||||
|
||||
const rightSegments: Segment[] = [];
|
||||
|
||||
const rightParts: string[] = [];
|
||||
if (ctx.model) {
|
||||
rightSegments.push({
|
||||
text: `model ${ctx.model.provider}/${ctx.model.id}`,
|
||||
fg: "white",
|
||||
bg: "blue",
|
||||
});
|
||||
rightParts.push(chip("model", `${ctx.model.provider}/${ctx.model.id}`, "text"));
|
||||
}
|
||||
|
||||
if (cost > 0) {
|
||||
rightSegments.push({
|
||||
text: `spent $${cost.toFixed(2)}`,
|
||||
fg: "black",
|
||||
bg: "yellow",
|
||||
});
|
||||
rightParts.push(chip("spent", `$${cost.toFixed(2)}`, "warning"));
|
||||
}
|
||||
const cxTone: Tone = cxPct >= 85 ? "error" : cxPct >= 60 ? "warning" : "success";
|
||||
rightParts.push(chip("ctx", `${Math.round(cxPct)}%`, cxTone));
|
||||
|
||||
let rightLine = join(rightParts);
|
||||
const maxRightWidth = Math.max(16, Math.floor(width * 0.55));
|
||||
if (visibleWidth(rightLine) > maxRightWidth) {
|
||||
rightLine = truncateToWidth(rightLine, maxRightWidth, ansiFg(SE.gray60, "..."));
|
||||
}
|
||||
|
||||
const cxColor = cxPct >= 85 ? "red" : cxPct >= 60 ? "yellow" : "green";
|
||||
rightSegments.push({
|
||||
text: `ctx ${Math.round(cxPct)}%`,
|
||||
fg: cxPct >= 85 ? "white" : "black",
|
||||
bg: cxColor,
|
||||
});
|
||||
|
||||
// Reserve space for right side
|
||||
const rightLine = renderPowerlineRight(rightSegments, width, theme);
|
||||
const rightWidth = visibleWidth(rightLine);
|
||||
|
||||
const leftLine = renderPowerline(
|
||||
leftSegments,
|
||||
Math.max(1, width - rightWidth),
|
||||
theme,
|
||||
);
|
||||
const leftWidth = visibleWidth(leftLine);
|
||||
|
||||
// Compose: left powerline + spaces to align + right powerline
|
||||
const gap = Math.max(0, width - leftWidth - rightWidth);
|
||||
const leftBudget = Math.max(1, width - rightWidth - 2);
|
||||
const leftLine = truncateToWidth(join(leftParts), leftBudget, ansiFg(SE.gray60, "..."));
|
||||
const gap = Math.max(1, width - visibleWidth(leftLine) - rightWidth);
|
||||
const line = leftLine + " ".repeat(gap) + rightLine;
|
||||
|
||||
return [truncateToWidth(line, width, theme.fg("dim", "…"))];
|
||||
return [truncateToWidth(line, width, ansiFg(SE.gray60, "..."))];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import { execFileSync } from "node:child_process";
|
||||
import { basename } from "node:path";
|
||||
|
||||
export interface GitStatus {
|
||||
repo: string | null;
|
||||
branch: string | null;
|
||||
dirty: boolean;
|
||||
untracked: boolean;
|
||||
|
|
@ -15,6 +17,20 @@ export interface GitStatus {
|
|||
let cache: GitStatus | null = null;
|
||||
let lastFetch = 0;
|
||||
|
||||
function getRepoName(cwd: string): string | null {
|
||||
try {
|
||||
const root = execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
||||
cwd,
|
||||
encoding: "utf-8",
|
||||
stdio: ["pipe", "pipe", "ignore"],
|
||||
timeout: 1500,
|
||||
}).trim();
|
||||
return root ? basename(root) : basename(cwd) || null;
|
||||
} catch {
|
||||
return basename(cwd) || null;
|
||||
}
|
||||
}
|
||||
|
||||
function getLastCommit(
|
||||
cwd: string,
|
||||
): { timeAgo: string; message: string } | null {
|
||||
|
|
@ -74,6 +90,7 @@ export function refreshGitStatus(cwd: string): GitStatus {
|
|||
if (now - lastFetch < 400 && cache) return cache;
|
||||
lastFetch = now;
|
||||
|
||||
const repo = getRepoName(cwd);
|
||||
let branch: string | null = null;
|
||||
try {
|
||||
branch =
|
||||
|
|
@ -85,6 +102,7 @@ export function refreshGitStatus(cwd: string): GitStatus {
|
|||
}).trim() || null;
|
||||
} catch {
|
||||
cache = {
|
||||
repo,
|
||||
branch: null,
|
||||
dirty: false,
|
||||
untracked: false,
|
||||
|
|
@ -136,9 +154,10 @@ export function refreshGitStatus(cwd: string): GitStatus {
|
|||
const diff = getDiffStats(cwd);
|
||||
const lastCommit = getLastCommit(cwd);
|
||||
|
||||
cache = { branch, dirty, untracked, ahead, behind, ...diff, lastCommit };
|
||||
cache = { repo, branch, dirty, untracked, ahead, behind, ...diff, lastCommit };
|
||||
} catch {
|
||||
cache = {
|
||||
repo,
|
||||
branch,
|
||||
dirty: false,
|
||||
untracked: false,
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ For native (Rust) changes:
|
|||
|
||||
```bash
|
||||
npm run build:native
|
||||
ldd native/npm/linux-x64-gnu/forge_engine.node | grep -E "not found" || echo "OK"
|
||||
ldd rust-engine/npm/linux-x64-gnu/forge_engine.node | grep -E "not found" || echo "OK"
|
||||
```
|
||||
|
||||
## Git Workflow
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ For LLM/provider/transport bugs, capture all of:
|
|||
For native-engine bugs (`forge_engine.node`):
|
||||
|
||||
```bash
|
||||
ldd native/npm/linux-x64-gnu/forge_engine.node 2>&1 | grep -E "not found|missing"
|
||||
ldd rust-engine/npm/linux-x64-gnu/forge_engine.node 2>&1 | grep -E "not found|missing"
|
||||
```
|
||||
|
||||
When evidence collection splits into independent streams, fan out with parallel subagents (`Explore` for repo search, `Plan` for design analysis). Keep the root-cause + repro doctrine in this skill.
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
"@singularity-forge/pi-agent-core": ["packages/pi-agent-core/src/index.ts"],
|
||||
"@singularity-forge/pi-tui": ["packages/pi-tui/src/index.ts"],
|
||||
"@singularity-forge/native": ["packages/native/src/index.ts"],
|
||||
"@singularity-forge/native/*": ["packages/native/src/*/index.ts"],
|
||||
"@singularity-forge/native/*": ["packages/rust-engine/src/*/index.ts"],
|
||||
"@singularity-forge/mcp-server": ["packages/mcp-server/src/index.ts"],
|
||||
"@singularity-forge/rpc-client": ["packages/rpc-client/src/index.ts"]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,80 +0,0 @@
|
|||
import { NextResponse, type NextRequest } from "next/server"
|
||||
|
||||
/**
|
||||
* Next.js middleware — validates bearer token and origin on all API routes.
|
||||
*
|
||||
* The SF_WEB_AUTH_TOKEN env var is set at server launch. Every /api/* request
|
||||
* must carry a matching `Authorization: Bearer <token>` header. EventSource
|
||||
* (SSE) connections may use the `_token` query parameter instead since the
|
||||
* EventSource API cannot set custom headers.
|
||||
*
|
||||
* Additionally, if an `Origin` header is present, it must match the expected
|
||||
* localhost origin to prevent cross-site request forgery.
|
||||
*/
|
||||
export function middleware(request: NextRequest): NextResponse | undefined {
|
||||
const { pathname } = request.nextUrl
|
||||
|
||||
// Only gate API routes
|
||||
if (!pathname.startsWith("/api/")) return NextResponse.next()
|
||||
|
||||
const expectedToken = process.env.SF_WEB_AUTH_TOKEN
|
||||
if (!expectedToken) {
|
||||
// If no token was configured (e.g. dev mode without launch harness),
|
||||
// allow everything — the server didn't opt into auth.
|
||||
return NextResponse.next()
|
||||
}
|
||||
|
||||
// ── Origin / CORS check ────────────────────────────────────────────
|
||||
const origin = request.headers.get("origin")
|
||||
if (origin) {
|
||||
const host = process.env.SF_WEB_HOST || "127.0.0.1"
|
||||
const port = process.env.SF_WEB_PORT || "3000"
|
||||
|
||||
// Default: localhost origin for the launched host:port
|
||||
const allowed = new Set([`http://${host}:${port}`])
|
||||
|
||||
// SF_WEB_ALLOWED_ORIGINS lets users whitelist additional origins for
|
||||
// secure tunnel setups (Tailscale Serve, Cloudflare Tunnel, ngrok, etc.)
|
||||
const extra = process.env.SF_WEB_ALLOWED_ORIGINS
|
||||
if (extra) {
|
||||
for (const entry of extra.split(",")) {
|
||||
const trimmed = entry.trim()
|
||||
if (trimmed) allowed.add(trimmed)
|
||||
}
|
||||
}
|
||||
|
||||
if (!allowed.has(origin)) {
|
||||
return NextResponse.json(
|
||||
{ error: "Forbidden: origin mismatch" },
|
||||
{ status: 403 },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// ── Bearer token check ─────────────────────────────────────────────
|
||||
let token: string | null = null
|
||||
|
||||
// 1. Authorization header (preferred)
|
||||
const authHeader = request.headers.get("authorization")
|
||||
if (authHeader?.startsWith("Bearer ")) {
|
||||
token = authHeader.slice(7)
|
||||
}
|
||||
|
||||
// 2. Query parameter fallback for EventSource / SSE
|
||||
if (!token) {
|
||||
token = request.nextUrl.searchParams.get("_token")
|
||||
}
|
||||
|
||||
if (!token || token !== expectedToken) {
|
||||
return NextResponse.json(
|
||||
{ error: "Unauthorized" },
|
||||
{ status: 401 },
|
||||
)
|
||||
}
|
||||
|
||||
return NextResponse.next()
|
||||
}
|
||||
|
||||
export const config = {
|
||||
matcher: "/api/:path*",
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue