Add architecture doc and research reports for taskgraph_ts napi wrapper
This commit is contained in:
266
docs/architecture.md
Normal file
266
docs/architecture.md
Normal file
@@ -0,0 +1,266 @@
|
||||
# taskgraph_ts Architecture
|
||||
|
||||
> Status: draft — this is a starting point for iteration, not a final spec.
|
||||
|
||||
## Why This Exists
|
||||
|
||||
The taskgraph CLI is useful but requires bash access. In agent systems, bash + untrusted data sources (web content, academic papers, etc.) is a security risk akin to SQL injection — adversarial content can instruct agents to exfiltrate data or take harmful actions through the shell. We've seen this in practice: researchers hiding prompt injections in academic papers using Unicode steganography that bypassed review systems.
|
||||
|
||||
Rather than restricting which agents get bash access and hoping nothing goes wrong, we expose the graph and cost-benefit operations as a library callable as a native tool — no shell involved.
|
||||
|
||||
The same math and graph code also serves a different purpose: implementation agents that *do* have bash access can call these operations directly as tools rather than shelling out to the CLI, which is faster and avoids argument parsing issues.
|
||||
|
||||
## Core Principle
|
||||
|
||||
**The graph algorithms and cost-benefit math are the value.** Markdown parsing, file discovery, and CLI output formatting are input/output concerns that belong to the caller, not to this library.
|
||||
|
||||
This crate is a standalone implementation. It copies the essential logic from `/workspace/@alkimiadev/taskgraph` but does not depend on it. The upstream CLI continues to exist for human use and offline analysis.
|
||||
|
||||
## What We Copy (Rewritten)
|
||||
|
||||
From the taskgraph Rust crate, adapted for library use:
|
||||
|
||||
- **DependencyGraph** — all algorithms:
|
||||
- Cycle detection (`hasCycles`, `findCycles`)
|
||||
- Topological sort (`topologicalOrder`)
|
||||
- Dependency queries (`dependencies`, `dependents`)
|
||||
- Parallel groups (`parallelGroups`)
|
||||
- Critical path (`criticalPath`)
|
||||
- Weighted critical path (`weightedCriticalPath`)
|
||||
- Bottleneck detection (`bottlenecks`)
|
||||
- DOT export (`toDot`)
|
||||
|
||||
- **Categorical enums** with their numeric methods:
|
||||
- `TaskScope` — `costEstimate()` (1.0–5.0)
|
||||
- `TaskRisk` — `successProbability()` (0.50–0.98)
|
||||
- `TaskImpact` — `weight()` (1.0–3.0)
|
||||
- `TaskLevel` — labeling, no numeric method currently
|
||||
|
||||
- **Cost-benefit math**:
|
||||
- `calculateTaskEv(p, scopeCost, impactWeight)` — expected value calculation
|
||||
- Risk-path finding (highest cumulative risk path)
|
||||
- Decomposition flagging (tasks that should be broken down)
|
||||
|
||||
- **Error types** — cleaned up, mapped to typed JS error classes
|
||||
|
||||
## What We Don't Copy
|
||||
|
||||
- `Task` / `TaskFrontmatter` — markdown-specific structs
|
||||
- `TaskCollection` / directory scanning — filesystem discovery
|
||||
- `Config` / `.taskgraph.toml` — CLI configuration
|
||||
- `clap` command definitions — CLI dispatch
|
||||
- `gray_matter` / `serde_yaml` — markdown frontmatter parsing
|
||||
- `chrono` — timestamp handling (the graph doesn't need it)
|
||||
|
||||
These may be added later as a opt-in feature but are not part of the core.
|
||||
|
||||
## Input Model
|
||||
|
||||
The graph must be constructable from multiple sources. The DB consumer sends query results. The markdown consumer sends parsed files. The programmatic consumer builds incrementally.
|
||||
|
||||
### TaskInput
|
||||
|
||||
The universal input shape for a task:
|
||||
|
||||
```typescript
|
||||
interface TaskInput {
|
||||
id: string
|
||||
name?: string
|
||||
dependsOn: string[]
|
||||
scope?: "single" | "narrow" | "moderate" | "broad" | "system"
|
||||
risk?: "trivial" | "low" | "medium" | "high" | "critical"
|
||||
impact?: "isolated" | "component" | "phase" | "project"
|
||||
level?: "planning" | "decomposition" | "implementation" | "review" | "research"
|
||||
priority?: "low" | "medium" | "high" | "critical"
|
||||
}
|
||||
```
|
||||
|
||||
All categorical fields are optional. The graph algorithms only need `id` and `dependsOn`. The cost-benefit and weighted-path functions need the categorical fields.
|
||||
|
||||
### DependencyEdge
|
||||
|
||||
For constructing from DB rows where tasks and edges are separate:
|
||||
|
||||
```typescript
|
||||
interface DependencyEdge {
|
||||
from: string // prerequisite task id
|
||||
to: string // dependent task id
|
||||
}
|
||||
```
|
||||
|
||||
### Construction Paths
|
||||
|
||||
```typescript
|
||||
// 1. From DB query results (the primary use case)
|
||||
const graph = DependencyGraph.fromRecords(tasks, edges)
|
||||
|
||||
// 2. Incremental construction (programmatic)
|
||||
const graph = new DependencyGraph()
|
||||
graph.addTask("a")
|
||||
graph.addTask("b")
|
||||
graph.addDependency("a", "b")
|
||||
|
||||
// 3. From TaskInput array (convenience, extracts id + dependsOn)
|
||||
const graph = DependencyGraph.fromTasks(tasks)
|
||||
```
|
||||
|
||||
Path 1 is what alkhub's coordinator agent will use most. Path 2 is for programmatic/testing use. Path 3 is where the categorical data attaches for weighted analysis.
|
||||
|
||||
### Markdown Support
|
||||
|
||||
Not in scope for v1. If needed later, it would be a separate module or package that parses markdown files into `TaskInput[]` and then feeds them into the graph. The parsing is straightforward (gray_matter/YAML) and is better kept outside the core library — callers with bash access already have the CLI.
|
||||
|
||||
## API Surface (Draft)
|
||||
|
||||
### DependencyGraph
|
||||
|
||||
The primary class. Wraps petgraph internally.
|
||||
|
||||
```typescript
|
||||
class DependencyGraph {
|
||||
// Construction
|
||||
static fromRecords(tasks: TaskInput[], edges: DependencyEdge[]): DependencyGraph
|
||||
static fromTasks(tasks: TaskInput[]): DependencyGraph
|
||||
addTask(id: string): void
|
||||
addDependency(from: string, to: string): void
|
||||
|
||||
// Queries
|
||||
hasCycles(): boolean
|
||||
findCycles(): string[][]
|
||||
topologicalOrder(): string[] | null
|
||||
dependencies(taskId: string): string[]
|
||||
dependents(taskId: string): string[]
|
||||
taskCount(): number
|
||||
|
||||
// Analysis
|
||||
parallelGroups(): string[][]
|
||||
criticalPath(): string[]
|
||||
weightedCriticalPath(weights: Record<string, number>): string[]
|
||||
bottlenecks(): Array<[string, number]>
|
||||
|
||||
// Export
|
||||
toDot(): string
|
||||
}
|
||||
```
|
||||
|
||||
### Categorical Enums
|
||||
|
||||
Exposed as JS string enums with numeric accessor methods:
|
||||
|
||||
```typescript
|
||||
// The enum values are strings (matching the DB and frontmatter conventions)
|
||||
// The numeric methods are exposed as standalone functions
|
||||
|
||||
function scopeCostEstimate(scope: TaskScope): number // 1.0–5.0
|
||||
function riskSuccessProbability(risk: TaskRisk): number // 0.50–0.98
|
||||
function impactWeight(impact: TaskImpact): number // 1.0–3.0
|
||||
|
||||
// Or as static methods on enum-like objects — TBD
|
||||
```
|
||||
|
||||
The exact JS API shape (string union types vs enum objects vs namespace + functions) is open for iteration. The Rust side is unambiguous — these are enums with `match`-based methods.
|
||||
|
||||
### Cost-Benefit Analysis
|
||||
|
||||
```typescript
|
||||
function calculateTaskEv(
|
||||
probability: number,
|
||||
scopeCost: number,
|
||||
impactWeight: number
|
||||
): number
|
||||
|
||||
function riskPath(
|
||||
graph: DependencyGraph,
|
||||
tasks: TaskInput[]
|
||||
): string[]
|
||||
|
||||
function shouldDecompose(task: TaskInput): boolean
|
||||
```
|
||||
|
||||
### Error Types
|
||||
|
||||
Typed JS error classes instead of flat strings:
|
||||
|
||||
```typescript
|
||||
class TaskgraphError extends Error {}
|
||||
class TaskNotFoundError extends TaskgraphError { taskId: string }
|
||||
class CircularDependencyError extends TaskgraphError { cycles: string[][] }
|
||||
class InvalidInputError extends TaskgraphError { field: string; message: string }
|
||||
```
|
||||
|
||||
This lets callers distinguish error types programmatically — important when an agent needs to decide how to recover.
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
taskgraph_ts/
|
||||
├── Cargo.toml # Rust crate config (cdylib, napi deps)
|
||||
├── build.rs # napi-build setup
|
||||
├── package.json # npm package config + napi targets
|
||||
├── src/
|
||||
│ ├── lib.rs # Crate root, module declarations
|
||||
│ ├── graph.rs # DependencyGraph + all algorithms
|
||||
│ ├── enums.rs # TaskScope, TaskRisk, TaskImpact, TaskLevel
|
||||
│ ├── cost_benefit.rs # EV calculation, risk-path, decompose
|
||||
│ ├── input.rs # TaskInput, DependencyEdge napi structs
|
||||
│ ├── error.rs # Error types + napi conversion
|
||||
│ └── napi_types.rs # napi attribute structs (or inline)
|
||||
├── index.js # Auto-generated by napi build
|
||||
├── index.d.ts # Auto-generated TypeScript declarations
|
||||
└── ts/
|
||||
└── index.ts # Hand-written wrapper layer
|
||||
# - Re-exports from native module
|
||||
# - Typed error classes
|
||||
# - Input validation
|
||||
# - Convenience helpers
|
||||
```
|
||||
|
||||
The `ts/` wrapper layer is where we add ergonomic value on top of the raw napi bindings. This is also where we'd handle things like making `weightedCriticalPath` accept either a weight map or a `TaskInput[]` that carries categorical data.
|
||||
|
||||
## Build & Distribution
|
||||
|
||||
- **Rust crate**: `taskgraph_ts` (or `taskgraph_napi` — TBD), compiled as `cdylib`
|
||||
- **napi-rs**: v3, with `async` + `tokio_rt` features (for future async I/O operations)
|
||||
- **Targets**: macOS x64/ARM64, Linux x64/ARM64, Windows x64
|
||||
- **Package**: `@alkdev/taskgraph` on npm, with per-platform optional dependencies
|
||||
- **TypeScript**: Auto-generated `.d.ts` from napi macros, plus hand-written wrapper
|
||||
|
||||
## Dependencies (Rust)
|
||||
|
||||
| Crate | Purpose |
|
||||
|-------|---------|
|
||||
| `napi` / `napi-derive` | Node-API bindings |
|
||||
| `napi-build` | Build script configuration |
|
||||
| `petgraph` | Directed graph data structure and algorithms |
|
||||
| `thiserror` | Error derive macros |
|
||||
| `serde` + `serde_json` | Serialization for napi object conversion |
|
||||
|
||||
Notably absent: `gray_matter`, `serde_yaml`, `chrono`, `clap`. The core doesn't need them.
|
||||
|
||||
## Open Questions
|
||||
|
||||
These will be resolved through iteration, not upfront design:
|
||||
|
||||
1. **Package name** — `@alkdev/taskgraph` vs something else. The npm namespace and whether to match the CLI crate name.
|
||||
|
||||
2. **Async boundary** — `DependencyGraph` operations are CPU-bound and synchronous. Should we offer `Promise`-wrapped versions anyway for non-blocking use in event-loop-sensitive environments? Or is that the caller's job?
|
||||
|
||||
3. **Task metadata on the graph** — Currently `DependencyGraph` only stores task IDs as node weights. For `weightedCriticalPath` and `riskPath`, the weight data comes from `TaskInput[]` passed alongside the graph. Should the graph store metadata (scope, risk, etc.) on nodes so callers don't need to pass it separately?
|
||||
|
||||
4. **Risk-path return type** — Should `riskPath` return just `string[]` (the path) or include the cumulative risk score? The CLI command outputs both.
|
||||
|
||||
5. **Enum representation in napi** — `#[napi(string_enum)]` gives JS string values, which aligns with the DB enum values. Or we could use `#[napi(enum)]` for numeric values with a TS mapping. String enums match the existing ecosystem better.
|
||||
|
||||
6. **Relationship to alkhub's graphology** — The alkhub spec currently uses graphology for runtime graph ops in the hub. This napi module could replace graphology for the algorithms it supports (and petgraph is faster). Or they could coexist — graphology for the coordinator's runtime queries, taskgraph_napi for deep analysis. Needs iteration to see what feels right.
|
||||
|
||||
7. **How `shouldDecompose` works** — The CLI `decompose` command checks if `scope > moderate` or `risk > medium`. Should this be a simple function, a method on a `Task` object, or configurable thresholds?
|
||||
|
||||
8. **Markdown feature flag** — Whether to ever add an optional markdown-parsing feature, and if so whether it lives in this crate or a companion package.
|
||||
|
||||
## Threat Model Context
|
||||
|
||||
For background on the security motivation:
|
||||
|
||||
- **Attack vector**: Agents with bash access processing untrusted content (web pages, academic papers, API responses) can be manipulated via prompt injection. This includes subtle attacks like Unicode steganography hiding instructions in otherwise legitimate content.
|
||||
- **Defense in depth**: The instruction firewall project (using Ternary Bonsai 1.7b classifier to detect instruction-bearing content) addresses detection. This project addresses the other side — reducing the blast radius by removing bash as a requirement for analysis operations.
|
||||
- **Tool-based access**: Instead of `taskgraph --json list | jq`, agents call `taskgraph.listTasks()` as a tool. No shell, no injection surface, no data exfiltration path through bash.
|
||||
718
docs/research/iroh_ts_reference.md
Normal file
718
docs/research/iroh_ts_reference.md
Normal file
@@ -0,0 +1,718 @@
|
||||
# iroh-ts Reference Implementation Research Report
|
||||
|
||||
**Project:** [iroh-ts](https://github.com/rayhanadev/iroh-ts) — TypeScript/JavaScript bindings for iroh (peer-to-peer QUIC networking)
|
||||
**Date:** 2026-04-23
|
||||
**Purpose:** Reference for building taskgraph-ts napi-rs wrapper
|
||||
|
||||
---
|
||||
|
||||
## 1. Project Structure
|
||||
|
||||
iroh-ts is a minimal, single-crate napi-rs project with a flat layout:
|
||||
|
||||
```
|
||||
iroh-ts/
|
||||
├── .cargo/
|
||||
│ └── config.toml # Cross-compilation rustflags (Windows static CRT)
|
||||
├── .github/
|
||||
│ └── workflows/
|
||||
│ └── CI.yml # GitHub Actions CI/CD
|
||||
├── .gitattributes # EOL normalization + mark generated files
|
||||
├── .gitignore # Node + Rust ignores
|
||||
├── build.rs # napi-build setup (1 line)
|
||||
├── Cargo.toml # Rust crate config
|
||||
├── bun.lock # Bun lockfile
|
||||
├── index.d.ts # Auto-generated TypeScript declarations
|
||||
├── index.js # Auto-generated JS loader (platform resolution)
|
||||
├── package.json # npm package config
|
||||
├── src/
|
||||
│ ├── lib.rs # Crate root: module declarations + init_logging
|
||||
│ ├── error.rs # IrohError enum + conversion helpers
|
||||
│ ├── endpoint.rs # Endpoint class (napi-wrapped)
|
||||
│ ├── connection.rs # Connection class + BiStreamResult struct
|
||||
│ └── stream.rs # SendStream + RecvStream classes
|
||||
├── tsconfig.json # TypeScript config (bundler mode, noEmit)
|
||||
└── README.md
|
||||
```
|
||||
|
||||
**Key observations:**
|
||||
- No separate `npm/` or `crates/` directory — it is a single Rust crate mapped to a single npm package
|
||||
- Generated `index.js` and `index.d.ts` are committed to the repo (auto-generated by `napi build`)
|
||||
- Platform-specific `.node` binaries are not committed; they are built in CI and published as separate npm packages
|
||||
- No test directory exists in the repo (tests would use the `ava` framework listed in bun.lock)
|
||||
|
||||
---
|
||||
|
||||
## 2. Cargo.toml Configuration
|
||||
|
||||
```toml
|
||||
[package]
|
||||
name = "iroh"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"] # Critical: produces a C dynamic library for Node
|
||||
|
||||
[dependencies]
|
||||
napi-derive = "3.0.0" # Procedural macro crate (#[napi] attribute)
|
||||
iroh = "0.95.1" # The wrapped Rust library
|
||||
iroh-base = "0.95.1"
|
||||
bytes = "1.11.0"
|
||||
thiserror = "2.0.17"
|
||||
tracing = "0.1.41"
|
||||
|
||||
[dependencies.napi]
|
||||
version = "3.0.0"
|
||||
features = ["async", "tokio_rt"] # Required for async Rust functions exposed to JS
|
||||
|
||||
[dependencies.tokio]
|
||||
version = "1.48.0"
|
||||
features = ["sync"] # Only sync feature (tokio_rt comes from napi)
|
||||
|
||||
[dependencies.tracing-subscriber]
|
||||
version = "0.3.20"
|
||||
features = ["env-filter"]
|
||||
|
||||
[build-dependencies]
|
||||
napi-build = "2" # Build script helper
|
||||
|
||||
[profile.release]
|
||||
lto = true # Link-Time Optimization for smaller binaries
|
||||
strip = "symbols" # Strip debug symbols for smaller binaries
|
||||
```
|
||||
|
||||
**Key takeaways:**
|
||||
- `crate-type = ["cdylib"]` — **mandatory** for napi-rs; produces `.node` shared library
|
||||
- `napi` and `napi-derive` are separate crates with matched major versions (both 3.0.0)
|
||||
- The `async` + `tokio_rt` features on napi are essential if you expose async Rust functions
|
||||
- `napi-build = "2"` is a minimal build dependency that just calls `napi_build::setup()`
|
||||
- Release profile uses LTO + symbol stripping — standard practice for napi-rs to minimize binary size
|
||||
|
||||
---
|
||||
|
||||
## 3. package.json Configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "@rayhanadev/iroh",
|
||||
"version": "0.1.1",
|
||||
"main": "index.js",
|
||||
"types": "index.d.ts",
|
||||
|
||||
"napi": {
|
||||
"binaryName": "iroh",
|
||||
"targets": [
|
||||
"x86_64-pc-windows-msvc",
|
||||
"x86_64-apple-darwin",
|
||||
"x86_64-unknown-linux-gnu",
|
||||
"aarch64-apple-darwin"
|
||||
]
|
||||
},
|
||||
|
||||
"scripts": {
|
||||
"artifacts": "napi artifacts",
|
||||
"build": "napi build --platform --release",
|
||||
"build:debug": "napi build --platform",
|
||||
"prepublishOnly": "napi prepublish -t npm",
|
||||
"preversion": "napi build --platform && git add .",
|
||||
"version": "napi version"
|
||||
},
|
||||
|
||||
"devDependencies": {
|
||||
"@emnapi/core": "^1.5.0",
|
||||
"@emnapi/runtime": "^1.5.0",
|
||||
"@napi-rs/cli": "^3.2.0",
|
||||
"@tybys/wasm-util": "^0.10.0"
|
||||
},
|
||||
|
||||
"peerDependencies": {
|
||||
"typescript": "^5"
|
||||
},
|
||||
|
||||
"optionalDependencies": {
|
||||
"@rayhanadev/iroh-win32-x64-msvc": "0.1.0",
|
||||
"@rayhanadev/iroh-darwin-x64": "0.1.0",
|
||||
"@rayhanadev/iroh-linux-x64-gnu": "0.1.0",
|
||||
"@rayhanadev/iroh-darwin-arm64": "0.1.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Key takeaways:**
|
||||
- The `napi` section in package.json is the core napi-rs config — it defines:
|
||||
- `binaryName`: the base name for the `.node` file (must match the crate name)
|
||||
- `targets`: which platform triples to build for (used by CI and `napi` CLI)
|
||||
- `--platform` flag on `napi build` makes it generate the correct platform-specific filename
|
||||
- `optionalDependencies` lists the per-platform npm packages — npm auto-selects the right one
|
||||
- These are separate npm packages published from the `npm/` directory created by `napi create-npm-dirs`
|
||||
- Names follow the pattern: `@scope/{binaryName}-{platform}-{arch}-{abi}`
|
||||
- `@emnapi/core` and `@emnapi/runtime` are for WASI/WASM support (optional, for browser targets)
|
||||
- No `@napi-rs/client` dependency — the CLI (`@napi-rs/cli`) is the only napi-rs JS dependency needed
|
||||
- The `prepublishOnly` script runs `napi prepublish` which handles version alignment across platform packages
|
||||
|
||||
---
|
||||
|
||||
## 4. Rust Function/Class Export Patterns via napi-rs Macros
|
||||
|
||||
### 4.1 Crate Root (`lib.rs`)
|
||||
|
||||
```rust
|
||||
#![deny(clippy::all)]
|
||||
|
||||
use napi_derive::napi;
|
||||
|
||||
mod connection;
|
||||
mod endpoint;
|
||||
mod error;
|
||||
mod stream;
|
||||
|
||||
pub use connection::*;
|
||||
pub use endpoint::*;
|
||||
pub use error::*;
|
||||
pub use stream::*;
|
||||
|
||||
#[napi]
|
||||
pub fn init_logging(level: Option<String>) {
|
||||
let filter = level.unwrap_or_else(|| "info".to_string());
|
||||
let _ = tracing_subscriber::fmt().with_env_filter(filter).try_init();
|
||||
}
|
||||
```
|
||||
|
||||
**Patterns:**
|
||||
- `#![deny(clippy::all)]` — strict linting at crate level
|
||||
- Module-based organization with `pub use` re-exports from the crate root
|
||||
- Free functions annotated with `#[napi]` become top-level JS exports
|
||||
- `Option<String>` maps to `string | undefined | null` in TypeScript
|
||||
|
||||
### 4.2 Class Pattern (Endpoint)
|
||||
|
||||
```rust
|
||||
#[napi]
|
||||
pub struct Endpoint {
|
||||
inner: Arc<iroh::Endpoint>, // Wrap the Rust type in Arc for shared ownership
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl Endpoint {
|
||||
#[napi(factory)] // Static factory method → Endpoint.create()
|
||||
pub async fn create() -> Result<Endpoint> { ... }
|
||||
|
||||
#[napi(factory)] // With options → Endpoint.createWithOptions(opts)
|
||||
pub async fn create_with_options(options: EndpointOptions) -> Result<Endpoint> { ... }
|
||||
|
||||
#[napi] // Instance method → endpoint.nodeId()
|
||||
pub fn node_id(&self) -> String { ... }
|
||||
|
||||
#[napi] // Instance method → endpoint.connect(addr, alpn)
|
||||
pub async fn connect(&self, addr: String, alpn: String) -> Result<Connection> { ... }
|
||||
}
|
||||
```
|
||||
|
||||
**Key patterns:**
|
||||
1. **Struct + impl separation:** The struct definition gets `#[napi]`, AND a separate `#[napi] impl` block exposes methods
|
||||
2. **`Arc<InnerType>`** pattern: Rust objects shared across JS/Rust boundary are wrapped in `Arc`. This is critical because:
|
||||
- Node.js is single-threaded but napi-rs creates a reference that may outlive the original scope
|
||||
- The Arc allows the JS wrapper to own a reference-counted Rust object
|
||||
3. **`#[napi(factory)]`**: Creates a static factory method instead of a constructor. This is the idiomatic way to handle async construction (since WASM/native constructors can't be async)
|
||||
4. **`snake_case` Rust → `camelCase` JS**: napi-rs auto-converts `node_id` to `nodeId`, `create_with_options` to `createWithOptions`
|
||||
5. **Return types:** `Result<T>` maps to JS `T | Error` (throws on Err)
|
||||
|
||||
### 4.3 Struct with Mutex for Interior Mutability (SendStream/RecvStream)
|
||||
|
||||
```rust
|
||||
#[derive(Clone)]
|
||||
#[napi]
|
||||
pub struct SendStream {
|
||||
inner: Arc<Mutex<iroh::endpoint::SendStream>>, // Arc<Mutex<T>> for mutable access
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl SendStream {
|
||||
pub async fn write(&self, data: Buffer) -> Result<u32> {
|
||||
let mut stream = self.inner.lock().await; // tokio Mutex for async-compatible locking
|
||||
let written = stream.write(&data).await.map_err(to_napi_error)?;
|
||||
Ok(written as u32)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Key patterns:**
|
||||
- `Arc<Mutex<T>>` with tokio's async Mutex — required when async methods need mutable access
|
||||
- `#[derive(Clone)]` because the `Arc<Mutex<>>` is cheaply cloneable
|
||||
- `Buffer` type from `napi::bindgen_prelude::*` maps to Node.js `Buffer`
|
||||
- Manual `as u32` cast because napi-rs doesn't support `usize` (platform-dependent size)
|
||||
|
||||
### 4.4 Object Struct (Options)
|
||||
|
||||
```rust
|
||||
#[napi(object)]
|
||||
#[derive(Default)]
|
||||
pub struct EndpointOptions {
|
||||
pub alpns: Option<Vec<String>>,
|
||||
pub secret_key: Option<String>,
|
||||
}
|
||||
```
|
||||
|
||||
**Pattern:**
|
||||
- `#[napi(object)]` makes this a plain JS object (not a class) — fields become properties
|
||||
- `#[derive(Default)]` allows easy construction on the Rust side when JS omits fields
|
||||
- `Option<T>` makes fields optional in the TypeScript definition
|
||||
|
||||
### 4.5 Getter Properties (BiStreamResult)
|
||||
|
||||
```rust
|
||||
#[napi]
|
||||
pub struct BiStreamResult {
|
||||
send: SendStream,
|
||||
recv: RecvStream,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl BiStreamResult {
|
||||
#[napi(getter)]
|
||||
pub fn get_send(&self) -> SendStream {
|
||||
self.send.clone()
|
||||
}
|
||||
|
||||
#[napi(getter)]
|
||||
pub fn get_recv(&self) -> RecvStream {
|
||||
self.recv.clone()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Pattern:**
|
||||
- Use `#[napi(getter)]` to expose Rust struct fields as JS getter properties
|
||||
- The `get_` prefix is stripped: `get_send` → `.send` property in JS
|
||||
- Must return cloned values since the Rust struct owns the data
|
||||
|
||||
### 4.6 Type Mappings (Rust → TypeScript)
|
||||
|
||||
| Rust Type | TypeScript Type |
|
||||
|-----------|---------------|
|
||||
| `String` | `string` |
|
||||
| `u32`, `f64` | `number` |
|
||||
| `bool` | `boolean` |
|
||||
| `Option<T>` | `T \| null \| undefined` |
|
||||
| `Option<u32>` | `number \| null` |
|
||||
| `Vec<String>` | `Array<string>` |
|
||||
| `Buffer` (from napi) | `Buffer` |
|
||||
| `Result<T>` | `T` (throws on Err) |
|
||||
| `Result<Option<T>>` | `T \| null` |
|
||||
| `#[napi] struct` | `class` |
|
||||
| `#[napi(object)] struct` | `interface` |
|
||||
|
||||
---
|
||||
|
||||
## 5. TypeScript Type Definitions
|
||||
|
||||
### Auto-Generated `index.d.ts`
|
||||
|
||||
The `index.d.ts` file is **entirely auto-generated** by `napi build --platform`. It is not hand-written.
|
||||
|
||||
Generated output structure:
|
||||
```typescript
|
||||
/* auto-generated by NAPI-RS */
|
||||
/* eslint-disable */
|
||||
|
||||
export declare class BiStreamResult {
|
||||
get send(): SendStream
|
||||
get recv(): RecvStream
|
||||
}
|
||||
|
||||
export declare class Connection {
|
||||
remoteNodeId(): string
|
||||
alpn(): Buffer
|
||||
openBi(): Promise<BiStreamResult>
|
||||
openUni(): Promise<SendStream>
|
||||
// ... etc
|
||||
}
|
||||
|
||||
export declare class Endpoint {
|
||||
static create(): Promise<Endpoint>
|
||||
static createWithOptions(options: EndpointOptions): Promise<Endpoint>
|
||||
nodeId(): string
|
||||
// ... etc
|
||||
}
|
||||
|
||||
export interface EndpointOptions {
|
||||
alpns?: Array<string>
|
||||
secretKey?: string
|
||||
}
|
||||
|
||||
export declare function initLogging(level?: string | undefined | null): void
|
||||
```
|
||||
|
||||
**Key observations:**
|
||||
- `#[napi]` structs become `declare class` with methods
|
||||
- `#[napi(object)]` structs become `interface`
|
||||
- `#[napi(factory)]` methods become `static` methods
|
||||
- `#[napi(getter)]` methods become `get` accessor properties
|
||||
- Doc comments from Rust (`/// ...`) are preserved as JSDoc in the `.d.ts`
|
||||
- Async Rust functions correctly become `Promise<T>` return types
|
||||
- The `@ts-nocheck` and `/* eslint-disable */` in `index.js` suppress linting of generated code
|
||||
|
||||
### tsconfig.json
|
||||
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["ESNext"],
|
||||
"target": "ESNext",
|
||||
"module": "Preserve",
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"noEmit": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This is a development-only config — the package doesn't ship compiled TS, only `.d.ts` declarations.
|
||||
|
||||
---
|
||||
|
||||
## 6. Build Pipeline
|
||||
|
||||
### 6.1 Local Development Build
|
||||
|
||||
```bash
|
||||
bun install # Install @napi-rs/cli + deps
|
||||
bun run build # napi build --platform --release
|
||||
```
|
||||
|
||||
What `napi build --platform --release` does:
|
||||
1. Invokes `cargo build --release` to compile the Rust crate as `cdylib`
|
||||
2. Copies the resulting `.so`/`.dylib`/`.dll` to the project root as `{binaryName}.{platform}.node`
|
||||
- e.g., `iroh.linux-x64-gnu.node`
|
||||
3. Generates `index.d.ts` (TypeScript declarations) from the `#[napi]` annotated code
|
||||
4. Generates `index.js` (CommonJS loader) that resolves the correct `.node` file per platform
|
||||
|
||||
### 6.2 CI Build (Cross-Compilation)
|
||||
|
||||
The CI matrix builds for 4 targets:
|
||||
|
||||
| Host Runner | Target | Build Command |
|
||||
|-------------|--------|---------------|
|
||||
| `macos-latest` | `x86_64-apple-darwin` | `bun run build --target x86_64-apple-darwin` |
|
||||
| `windows-latest` | `x86_64-pc-windows-msvc` | `bun run build --target x86_64-pc-windows-msvc` |
|
||||
| `ubuntu-latest` | `x86_64-unknown-linux-gnu` | `bun run build --target x86_64-unknown-linux-gnu --use-napi-cross` |
|
||||
| `macos-latest` | `aarch64-apple-darwin` | `bun run build --target aarch64-apple-darwin` |
|
||||
|
||||
Cross-compilation notes:
|
||||
- **macOS → aarch64**: Uses `dtolnay/rust-toolchain` with `targets` parameter; macOS cross-compilation "just works" with Rust
|
||||
- **Linux x64**: Uses `--use-napi-cross` flag which downloads cross-compilation toolchains via `@napi-rs/cross-toolchain`
|
||||
- **Musl targets**: Would use `cargo-zigbuild` via Zig (configured in CI but not in the current target matrix)
|
||||
- **Windows**: Uses MSVC target; `.cargo/config.toml` sets `+crt-static` for static CRT linking
|
||||
|
||||
### 6.3 Artifacts and Publishing
|
||||
|
||||
1. Each matrix job uploads its `.node` file as a GitHub artifact
|
||||
2. The `publish` job:
|
||||
- Downloads all artifacts
|
||||
- Runs `napi create-npm-dirs` to create `npm/` directory structure
|
||||
- Runs `napi artifacts` to move `.node` files into per-platform npm packages under `npm/`
|
||||
- Publishes the main package + all platform packages to npm
|
||||
|
||||
### 6.4 The `index.js` Loader
|
||||
|
||||
The auto-generated `index.js` is ~580 lines and handles:
|
||||
- Platform detection (`process.platform` + `process.arch`)
|
||||
- musl vs glibc detection on Linux (tries filesystem, process report, and child process)
|
||||
- Resolution ordering: try local `.node` file first, then fall back to `@scope/pkg-platform` npm package
|
||||
- `NAPI_RS_NATIVE_LIBRARY_PATH` environment variable override for custom paths
|
||||
- WASI/WASM fallback for browser environments
|
||||
- Version mismatch checking between main package and platform packages
|
||||
|
||||
---
|
||||
|
||||
## 7. Error Handling Across the Rust/TS Boundary
|
||||
|
||||
### 7.1 Error Definition Pattern
|
||||
|
||||
```rust
|
||||
#[derive(Debug, Error)]
|
||||
pub enum IrohError {
|
||||
#[error("Bind error: {0}")]
|
||||
Bind(String),
|
||||
#[error("Connect error: {0}")]
|
||||
Connect(String),
|
||||
#[error("Stream error: {0}")]
|
||||
Stream(String),
|
||||
#[error("Parse error: {0}")]
|
||||
Parse(String),
|
||||
#[error("Connection closed: {0}")]
|
||||
ConnectionClosed(String),
|
||||
#[error("Internal error: {0}")]
|
||||
Internal(String),
|
||||
}
|
||||
```
|
||||
|
||||
### 7.2 Error Conversion
|
||||
|
||||
Two approaches are used:
|
||||
|
||||
**Approach 1: `From` trait implementation**
|
||||
```rust
|
||||
impl From<IrohError> for napi::Error {
|
||||
fn from(err: IrohError) -> Self {
|
||||
napi::Error::from_reason(err.to_string())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Approach 2: Generic helper function (preferred for inline use)**
|
||||
```rust
|
||||
pub fn to_napi_error<E: std::fmt::Display>(err: E) -> napi::Error {
|
||||
napi::Error::from_reason(err.to_string())
|
||||
}
|
||||
```
|
||||
|
||||
### 7.3 Usage in Methods
|
||||
|
||||
```rust
|
||||
pub async fn connect(&self, addr: String, alpn: String) -> Result<Connection> {
|
||||
let endpoint_id: iroh::EndpointId = addr
|
||||
.parse()
|
||||
.map_err(|e| to_napi_error(format!("Invalid node ID: {e}")))?; // Add context
|
||||
|
||||
let conn = self.inner.connect(endpoint_id, alpn.as_bytes())
|
||||
.await
|
||||
.map_err(to_napi_error)?; // Simple conversion
|
||||
|
||||
Ok(Connection::new(conn))
|
||||
}
|
||||
```
|
||||
|
||||
**Key patterns:**
|
||||
- All Rust errors are converted to `napi::Error::from_reason(string)` — they become JS `Error` objects with a `.message` property
|
||||
- `thiserror` is used on the Rust side for structured error categories, but these are **flattened to strings** at the boundary
|
||||
- The `to_napi_error` helper is more flexible than the `From` impl because it works with any `Display` type
|
||||
- Context is added manually with `format!("Context: {e}")` wrapping, since the error types lose structure at the boundary
|
||||
- `Result<T>` in Rust maps to throwing in JS (no explicit `Result` type in TypeScript)
|
||||
|
||||
### 7.4 TypeScript Side
|
||||
|
||||
On the TS side, errors appear as standard JavaScript `Error` objects thrown from native code. There is no typed error handling — everything is a string message:
|
||||
|
||||
```typescript
|
||||
try {
|
||||
const conn = await endpoint.connect(addr, alpn);
|
||||
} catch (err) {
|
||||
// err is a standard Error with a message like "Connect error: connection refused"
|
||||
}
|
||||
```
|
||||
|
||||
**Critique:** This is a weak point of iroh-ts. Structured error information (error kind, codes) is lost at the boundary. For taskgraph-ts, we should consider:
|
||||
- Using `napi::Error::from_status_with_message()` with custom status codes
|
||||
- Or defining a custom error class on the JS side that parses the error string
|
||||
- Or using napi-rs's built-in `#[napi(ts_type)]` for more detailed type info
|
||||
|
||||
---
|
||||
|
||||
## 8. CI/CD and Cross-Compilation Setup
|
||||
|
||||
### 8.1 GitHub Actions Workflow
|
||||
|
||||
**Jobs:**
|
||||
1. **`lint`** (ubuntu-latest): `cargo fmt -- --check` + `cargo clippy`
|
||||
2. **`build`** (matrix): Cross-compile for 4 targets, upload `.node` artifacts
|
||||
3. **`publish`** (ubuntu-latest): Download artifacts, create npm dirs, publish to npm
|
||||
|
||||
**Key CI details:**
|
||||
- Bun is used instead of npm/yarn (`oven-sh/setup-bun@v1`)
|
||||
- Rust toolchain via `dtolnay/rust-toolchain@stable` with `components: clippy, rustfmt`
|
||||
- Cargo cache keyed by `target-host` pair
|
||||
- Zig + cargo-zigbuild installed conditionally for musl targets (though no musl targets in current matrix)
|
||||
- Version-based publishing: only publishes if the latest commit message matches a semver pattern
|
||||
- `npm config set provenance true` for supply chain security
|
||||
- Sequential `needs` dependency: `build` requires `lint`, `publish` requires both
|
||||
|
||||
### 8.2 Cross-Compilation Config
|
||||
|
||||
**`.cargo/config.toml`:**
|
||||
```toml
|
||||
[target.x86_64-pc-windows-msvc]
|
||||
rustflags = ["-C", "target-feature=+crt-static"]
|
||||
```
|
||||
|
||||
This ensures the Windows MSVC build statically links the C runtime, avoiding DLL dependency issues.
|
||||
|
||||
### 8.3 Platform Package Structure
|
||||
|
||||
When published, napi-rs creates a directory structure like:
|
||||
```
|
||||
npm/
|
||||
├── darwin-arm64/ → @rayhanadev/iroh-darwin-arm64
|
||||
├── darwin-x64/ → @rayhanadev/iroh-darwin-x64
|
||||
├── linux-x64-gnu/ → @rayhanadev/iroh-linux-x64-gnu
|
||||
└── win32-x64-msvc/ → @rayhanadev/iroh-win32-x64-msvc
|
||||
```
|
||||
|
||||
Each contains a minimal `package.json` with `os` + `cpu` constraints and the `.node` file.
|
||||
|
||||
---
|
||||
|
||||
## 9. napi-rs Version and Dependency Pinning
|
||||
|
||||
| Package | Version | Notes |
|
||||
|---------|---------|-------|
|
||||
| `napi` (Rust) | `3.0.0` | Core runtime crate |
|
||||
| `napi-derive` (Rust) | `3.0.0` | Procedural macros (must match napi major) |
|
||||
| `napi-build` (Rust) | `2` | Build script helper |
|
||||
| `@napi-rs/cli` (JS) | `^3.2.0` | CLI tool (resolved to 3.4.1 in lock) |
|
||||
|
||||
**Important:** `napi` and `napi-derive` MUST share the same major version. Version 3.x is the latest stable line.
|
||||
|
||||
The `features = ["async", "tokio_rt"]` on the `napi` crate are critical:
|
||||
- `async`: Enables `#[napi]` on async functions
|
||||
- `tokio_rt`: Uses the tokio runtime for async napi-rs tasks (there is also `napi4` feature for custom threads)
|
||||
|
||||
---
|
||||
|
||||
## 10. Patterns Worth Replicating and Avoiding
|
||||
|
||||
### Patterns to Replicate
|
||||
|
||||
1. **Arc wrapping for shared Rust types** — `Arc<InnerType>` for structs that are shared across the JS/Rust boundary. This is essential for correct lifetime management.
|
||||
|
||||
2. **`#[napi(factory)]` for async constructors** — Since JS constructors can't be async, use factory methods that return `Promise<T>`. The `Endpoint::create()` pattern is clean and idiomatic.
|
||||
|
||||
3. **Module-per-domain** — Separate files for each domain concept (endpoint, connection, stream, error). Clean separation of concerns.
|
||||
|
||||
4. **`#[napi(object)]` for options structs** — Clean mapping of Rust option structs to JS plain objects without class overhead.
|
||||
|
||||
5. **`to_napi_error` helper** — Generic error conversion function that works with any `Display` type. Much more ergonomic than writing `From` impls for every error type.
|
||||
|
||||
6. **Release profile optimization** — `lto = true` + `strip = "symbols"` significantly reduces binary size.
|
||||
|
||||
7. **Doc comments preserved** — Rust doc comments (`///`) flow through to TypeScript declarations, providing IDE autocomplete.
|
||||
|
||||
8. **`.gitattributes` marking generated files** — Using `linguist-detectable=false` for `index.js`, `index.d.ts`, and WASM files keeps GitHub language stats clean.
|
||||
|
||||
9. **Separate `impl` blocks** — Having a non-`#[napi]` `impl` block for the `new()` constructor and a `#[napi] impl` block for JS-exposed methods cleanly separates internal Rust API from external JS API.
|
||||
|
||||
10. **Platform-specific cargo config** — The `.cargo/config.toml` with `+crt-static` for Windows avoids runtime DLL issues.
|
||||
|
||||
### Patterns to Improve Upon
|
||||
|
||||
1. **Error handling is lossy** — All structured Rust errors are flattened to strings. Consider:
|
||||
- Custom napi error classes that preserve error categories
|
||||
- Error status codes using `napi::Error` with custom `Status` values
|
||||
- A richer error object on the JS side
|
||||
|
||||
2. **No test infrastructure** — The project has no unit tests. For taskgraph-ts, we should add:
|
||||
- Rust unit tests for internal logic
|
||||
- JS/TS integration tests that verify the napi boundary
|
||||
- CI test step that runs after build
|
||||
|
||||
3. **No TypeScript wrapper layer** — `index.d.ts` is purely auto-generated. Consider adding a hand-written `src/index.ts` that:
|
||||
- Re-exports from the native module
|
||||
- Adds convenience methods
|
||||
- Provides typed error classes
|
||||
- Adds input validation before calling native code
|
||||
|
||||
4. **Missing musl/Linux ARM64 targets** — Only 4 targets in the `napi` config. For broader compatibility, consider adding:
|
||||
- `aarch64-unknown-linux-gnu` (ARM Linux, AWS Graviton)
|
||||
- `x86_64-unknown-linux-musl` (Alpine Docker images)
|
||||
|
||||
5. **No `Cargo.lock` committed** — The `.gitignore` excludes `Cargo.lock`. For a binary crate (as opposed to a library), it's generally recommended to commit the lock file for reproducible builds. However, napi-rs projects often skip this since the CI builds from scratch.
|
||||
|
||||
6. **No `napi.config.js` or `napi.config.ts`** — Some larger napi-rs projects use a dedicated config file. For a small project, `package.json` `napi` section is fine, but it doesn't scale well.
|
||||
|
||||
7. **`Arc<Mutex<T>>` everywhere** — The SendStream/RecvStream use `tokio::sync::Mutex` even for read-only operations (like `id()`). Consider whether some operations could use `Arc<tokio::sync::RwLock>` or even `std::sync::RwLock` for non-async read paths.
|
||||
|
||||
8. **`init_logging` as a public API** — Exposing `tracing_subscriber::fmt().try_init()` is fragile: it can only succeed once, and multiple calls silently fail. Consider returning a boolean/result indicating whether initialization succeeded.
|
||||
|
||||
9. **No version consistency checks** — The Rust crate version (`0.1.0`) and npm package version (`0.1.1`) differ. This could cause confusion. Consider aligning them or using a script to keep them in sync.
|
||||
|
||||
10. **No `#[napi(custom_finalize)]`** — Missing custom finalize/destructor logic. If Rust objects need cleanup (e.g., flushing streams before drop), the default finalization may not be sufficient.
|
||||
|
||||
---
|
||||
|
||||
## Summary: Recommended Architecture for taskgraph-ts
|
||||
|
||||
Based on this research, here is a recommended approach:
|
||||
|
||||
### Cargo.toml
|
||||
```toml
|
||||
[package]
|
||||
name = "taskgraph"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
napi-derive = "3.0.0"
|
||||
# taskgraph = "..." (the Rust library being wrapped)
|
||||
thiserror = "2"
|
||||
tracing = "0.1"
|
||||
|
||||
[dependencies.napi]
|
||||
version = "3.0.0"
|
||||
features = ["async", "tokio_rt"]
|
||||
|
||||
[build-dependencies]
|
||||
napi-build = "2"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
strip = "symbols"
|
||||
```
|
||||
|
||||
### package.json
|
||||
```json
|
||||
{
|
||||
"name": "@alkdev/taskgraph",
|
||||
"main": "index.js",
|
||||
"types": "index.d.ts",
|
||||
"napi": {
|
||||
"binaryName": "taskgraph",
|
||||
"targets": [
|
||||
"x86_64-apple-darwin",
|
||||
"aarch64-apple-darwin",
|
||||
"x86_64-unknown-linux-gnu",
|
||||
"aarch64-unknown-linux-gnu",
|
||||
"x86_64-pc-windows-msvc"
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"build": "napi build --platform --release",
|
||||
"build:debug": "napi build --platform",
|
||||
"artifacts": "napi artifacts",
|
||||
"prepublishOnly": "napi prepublish -t npm",
|
||||
"version": "napi version"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@napi-rs/cli": "^3.2.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Rust Source Layout
|
||||
```
|
||||
src/
|
||||
├── lib.rs # Module declarations + any free functions
|
||||
├── error.rs # Error types + to_napi_error helper
|
||||
├── graph.rs # Graph class (#[napi] struct + impl)
|
||||
├── task.rs # Task class
|
||||
├── executor.rs # Executor class (async factory methods)
|
||||
└── types.rs # Shared types, #[napi(object)] structs
|
||||
```
|
||||
|
||||
### Key Conventions to Adopt
|
||||
1. `Arc<T>` for all napi struct inner types
|
||||
2. `#[napi(factory)]` for async constructors
|
||||
3. `#[napi(object)]` for option/config structs
|
||||
4. `to_napi_error` generic helper for error conversion
|
||||
5. Preserve doc comments for auto-generated `.d.ts`
|
||||
6. Always match `napi` and `napi-derive` major versions
|
||||
7. Use `tokio::sync::Mutex` for async interior mutability
|
||||
8. Add `.cargo/config.toml` with Windows CRT static linking
|
||||
9. Use `bun` as the JS runtime (fast, napi-rs compatible)
|
||||
10. Commit generated `index.js` and `index.d.ts` to the repo
|
||||
1270
docs/research/napi_rs_framework.md
Normal file
1270
docs/research/napi_rs_framework.md
Normal file
File diff suppressed because it is too large
Load Diff
1140
docs/research/taskgraph_rust_source.md
Normal file
1140
docs/research/taskgraph_rust_source.md
Normal file
File diff suppressed because it is too large
Load Diff
172
docs/research/taskgraph_ts_current_state.md
Normal file
172
docs/research/taskgraph_ts_current_state.md
Normal file
@@ -0,0 +1,172 @@
|
||||
# TaskGraph_TS — Current State Research Report
|
||||
|
||||
**Date:** 2026-04-23
|
||||
**Repository:** git@git.alk.dev:alkdev/taskgraph_ts.git
|
||||
**Branch:** main
|
||||
**Commit:** 1517b54 ("inital setup")
|
||||
|
||||
---
|
||||
|
||||
## 1. File Structure
|
||||
|
||||
The repository is in an extremely early, freshly-initialized state. The entire non-git content consists of only two files:
|
||||
|
||||
```
|
||||
/workspace/@alkdev/taskgraph_ts/
|
||||
├── .git/ # Git repository data
|
||||
├── AGENTS.md # Agent/instructions file (32 lines)
|
||||
└── docs/
|
||||
└── research/
|
||||
└── .gitkeep # Empty placeholder to keep directory in git
|
||||
```
|
||||
|
||||
That is the **complete** file tree. There are no source files, no configuration files, no tests, no documentation beyond what is listed above.
|
||||
|
||||
---
|
||||
|
||||
## 2. Configuration Files
|
||||
|
||||
### What exists
|
||||
|
||||
| File | Status |
|
||||
|---|---|
|
||||
| `package.json` | **Does not exist** |
|
||||
| `tsconfig.json` | **Does not exist** |
|
||||
| `Cargo.toml` | **Does not exist** |
|
||||
| `tslint.json` / `.eslintrc*` | **Does not exist** |
|
||||
| `.prettierrc*` | **Does not exist** |
|
||||
| `.gitignore` | **Does not exist** |
|
||||
| `.editorconfig` | **Does not exist** |
|
||||
| `README.md` | **Does not exist** |
|
||||
| `LICENSE` | **Does not exist** |
|
||||
| Any Makefile / build script | **Does not exist** |
|
||||
|
||||
### Summary
|
||||
|
||||
There are **zero configuration files** in the repository. No package manager is initialized. No TypeScript compiler configuration exists. No linters, formatters, or CI/CD pipelines have been set up. The project name `taskgraph_ts` implies a TypeScript project, but no `package.json` or `tsconfig.json` has been created yet.
|
||||
|
||||
---
|
||||
|
||||
## 3. Existing Code / Boilerplate
|
||||
|
||||
There is **no source code** in the repository. No `src/` directory exists. No `lib/`, `bin/`, `test/`, or `examples/` directories exist. No `.ts`, `.js`, `.rs`, or any other source files are present.
|
||||
|
||||
The only non-git file with actual content is `AGENTS.md`, which is a meta-tool instruction file (not project code). The only other file is `docs/research/.gitkeep`, which is an empty placeholder.
|
||||
|
||||
---
|
||||
|
||||
## 4. AGENTS.md File Contents
|
||||
|
||||
The `AGENTS.md` file is 32 lines long and contains instructions for AI agent memory/tool usage. Full content:
|
||||
|
||||
```markdown
|
||||
## Memory Tools
|
||||
|
||||
You have access to two tools for managing your context and accessing session history:
|
||||
|
||||
### memory({tool: "...", args: {...}})
|
||||
|
||||
Read-only tool for introspecting your session history and context state. Available operations:
|
||||
- `memory({tool: "help"})` — full reference with examples
|
||||
- `memory({tool: "summary"})` — quick counts of projects, sessions, messages, todos
|
||||
- `memory({tool: "sessions"})` — list recent sessions (useful for finding past work)
|
||||
- `memory({tool: "messages", args: {sessionId: "..."}})` — read a session's conversation
|
||||
- `memory({tool: "search", args: {query: "..."}})` — search across all conversations
|
||||
- `memory({tool: "compactions", args: {sessionId: "..."}})` — view compaction checkpoints
|
||||
- `memory({tool: "context"})` — check your current context usage
|
||||
|
||||
### memory_compact()
|
||||
|
||||
Trigger compaction on the current session. This summarizes the conversation so far to free context space.
|
||||
|
||||
**When to use memory_compact:**
|
||||
- When context is above 80% (check with `memory({tool: "context"})`)
|
||||
- When you notice you're losing track of earlier conversation details
|
||||
- At natural breakpoints in multi-step tasks (after completing a subtask, before starting a new one)
|
||||
- When the system prompt shows a yellow/red/critical context warning
|
||||
- Proactively, rather than waiting for automatic compaction at 92%
|
||||
|
||||
**When NOT to use memory_compact:**
|
||||
- When context is below 50% (it wastes a compaction cycle)
|
||||
- In the middle of a complex edit that you need immediate context for
|
||||
- When the task is nearly complete (just finish the task instead)
|
||||
|
||||
Compaction preserves your most important context in a structured summary — you will continue the session with the summary as your starting point.
|
||||
```
|
||||
|
||||
This file is **not** project-specific documentation — it is a shared instruction set for AI coding agents describing how to use memory introspection and compaction tools. It contains no information about the `taskgraph_ts` project's design, architecture, or goals.
|
||||
|
||||
---
|
||||
|
||||
## 5. Documentation and Design Notes
|
||||
|
||||
There is **no project documentation** in the repository. Key absences:
|
||||
|
||||
- No `README.md` describing the project
|
||||
- No design documents or architecture notes
|
||||
- No ADRs (Architecture Decision Records)
|
||||
- No API specifications
|
||||
- No diagrams or schematics
|
||||
- The `docs/research/` directory exists but contains only an empty `.gitkeep` file — it was created as a placeholder for future research documents (like this one)
|
||||
|
||||
---
|
||||
|
||||
## 6. Git History
|
||||
|
||||
### Full Git Log
|
||||
|
||||
```
|
||||
1517b54 inital setup
|
||||
```
|
||||
|
||||
There is exactly **one commit** in the repository, authored on 2026-04-23:
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| **Commit** | `1517b5459e1d79388a96057d17a72ac53064a068` |
|
||||
| **Author** | glm-5.1 <glm-5.1@alk.dev> |
|
||||
| **Date** | Thu Apr 23 08:13:39 2026 +0000 |
|
||||
| **Message** | "inital setup" (note: typo — "inital" instead of "initial") |
|
||||
|
||||
### Files in that commit
|
||||
|
||||
| File | Change |
|
||||
|---|---|
|
||||
| `AGENTS.md` | Added (32 lines) |
|
||||
| `docs/research/.gitkeep` | Added (empty file) |
|
||||
|
||||
### Branches
|
||||
|
||||
| Branch | Status |
|
||||
|---|---|
|
||||
| `main` (local) | Current branch, up to date with remote |
|
||||
| `remotes/origin/main` | Remote tracking branch |
|
||||
|
||||
### Remote
|
||||
|
||||
```
|
||||
origin git@git.alk.dev:alkdev/taskgraph_ts.git (fetch)
|
||||
origin git@git.alk.dev:alkdev/taskgraph_ts.git (push)
|
||||
```
|
||||
|
||||
### Working tree status
|
||||
|
||||
Clean — no uncommitted changes, no untracked files.
|
||||
|
||||
---
|
||||
|
||||
## Summary and Key Observations
|
||||
|
||||
1. **This is a blank-slate project.** The repository was just initialized with a single commit containing only an agent instructions file and an empty directory placeholder. No code, configuration, or documentation has been written yet.
|
||||
|
||||
2. **The project name implies TypeScript.** `taskgraph_ts` strongly suggests a TypeScript implementation of a "task graph" — likely a DAG (Directed Acyclic Graph) based task scheduling/execution system. However, no TypeScript tooling (`package.json`, `tsconfig.json`) has been set up.
|
||||
|
||||
3. **No `.gitignore` exists.** This should be created before adding any source files to avoid accidentally committing `node_modules/`, build artifacts, etc.
|
||||
|
||||
4. **No README or design docs.** There are no records of the project's purpose, scope, or intended architecture. Any future work will need to define these from scratch.
|
||||
|
||||
5. **The `docs/research/` directory** was explicitly created as a placeholder, suggesting the project founders intended for research documentation to be generated — which is what this report fulfills.
|
||||
|
||||
6. **Single author so far.** The only commit was by `glm-5.1@alk.dev`, an AI agent, indicating the project was initialized programmatically.
|
||||
|
||||
7. **Everything needs to be built from scratch:** package initialization, TypeScript configuration, source code structure, testing framework, linting/formatting, CI/CD, documentation, and the actual task graph implementation itself.
|
||||
Reference in New Issue
Block a user