Decompose architecture into 28 atomic implementation tasks

Break the @alkdev/taskgraph architecture specs into dependency-ordered
implementation tasks across 8 component directories: setup, schema,
error, graph, analysis, cost-benefit, frontmatter, api, and review.
Each task has clear acceptance criteria referencing specific architecture
docs. Three review tasks serve as quality gates at critical junction
points (schemas-and-errors, graph-complete, complete-library). The
dependency graph is validated acyclic with 9 topological levels enabling
significant parallelism across independent work streams.
This commit is contained in:
2026-04-27 08:30:05 +00:00
parent e592caed57
commit 131e3e929b
28 changed files with 1306 additions and 0 deletions

View File

@@ -0,0 +1,38 @@
---
id: analysis/bottlenecks
name: Implement bottlenecks analysis function
status: pending
depends_on:
- graph/construction
- graph/queries
scope: narrow
risk: low
impact: component
level: implementation
---
## Description
Implement `bottlenecks(graph: TaskGraph): Array<{ taskId: string; score: number }>` using `graphology-metrics` betweenness centrality. Bottleneck tasks are those on the most shortest paths between other nodes.
## Acceptance Criteria
- [ ] `bottlenecks` returns array of `{ taskId, score }` objects sorted by score descending
- [ ] Uses `graphology-metrics` betweenness centrality computation
- [ ] Normalized scores (0.01.0 range)
- [ ] Tasks with score 0 are still included (they're not bottlenecks)
- [ ] Works on disconnected graphs (betweenness is 0 for disconnected components)
- [ ] Unit tests: linear chain (middle node has highest betweenness), star graph (center has highest), independent nodes (all zero)
## References
- docs/architecture/api-surface.md — bottlenecks signature
- docs/architecture/build-distribution.md — graphology-metrics dependency
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

View File

@@ -0,0 +1,40 @@
---
id: analysis/critical-path
name: Implement criticalPath and weightedCriticalPath functions
status: pending
depends_on:
- graph/construction
- graph/queries
scope: moderate
risk: medium
impact: component
level: implementation
---
## Description
Implement `criticalPath` and `weightedCriticalPath` as standalone functions. `criticalPath` finds the longest path from sources to sinks using default edge weighting. `weightedCriticalPath` accepts a custom weight function for per-node weighting.
`criticalPath` can be implemented via topological order + dynamic programming (longest path in DAG). For unweighted, each edge has weight 1; for weighted, each node contributes a weight.
## Acceptance Criteria
- [ ] `criticalPath(graph: TaskGraph): string[]` — returns the longest path as an ordered array of task IDs
- [ ] `weightedCriticalPath(graph: TaskGraph, weightFn: (taskId: string, attrs: TaskGraphNodeAttributes) => number): string[]` — returns the path with the highest cumulative weight
- [ ] Both functions throw `CircularDependencyError` if graph is cyclic
- [ ] When multiple paths tie, returns any one of them (deterministic order preferred)
- [ ] Empty graph returns `[]`; single-node graph returns `[nodeId]`
- [ ] Unit tests: linear chain (the chain itself is critical path), diamond graph (tests path selection), weighted variant with diverse scope values
## References
- docs/architecture/api-surface.md — criticalPath, weightedCriticalPath signatures
- docs/architecture/graph-model.md — edge direction (prerequisite→dependent determines source→sink)
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

View File

@@ -0,0 +1,38 @@
---
id: analysis/parallel-groups
name: Implement parallelGroups analysis function
status: pending
depends_on:
- graph/construction
- graph/queries
scope: narrow
risk: low
impact: component
level: implementation
---
## Description
Implement `parallelGroups(graph: TaskGraph): string[][]` in `src/analysis/index.ts` or a dedicated module. This returns groups of tasks that can be executed concurrently — tasks at the same topological depth. Uses `graphology-dag.topologicalGenerations`.
## Acceptance Criteria
- [ ] `parallelGroups` returns `string[][]` where each inner array is a generation of tasks at the same depth from sources
- [ ] Uses `graphology-dag.topologicalGenerations()` for the generation computation
- [ ] Tasks with zero prerequisites are in the first group
- [ ] Throws `CircularDependencyError` if the graph is cyclic (delegated to `topologicalGenerations` behavior)
- [ ] Works on disconnected graphs (each connected component sorted independently, then merged by depth)
- [ ] Unit tests: linear chain (each group size 1), diamond graph, disconnected components
## References
- docs/architecture/api-surface.md — parallelGroups signature
- docs/architecture/graph-model.md — parallel groups definition
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

View File

@@ -0,0 +1,57 @@
---
id: api/public-exports
name: Wire up public API surface in src/index.ts
status: pending
depends_on:
- graph/taskgraph-class
- graph/construction
- graph/mutation
- graph/queries
- graph/subgraph-and-validation
- graph/export
- analysis/parallel-groups
- analysis/critical-path
- analysis/bottlenecks
- cost-benefit/ev-calculation
- cost-benefit/dag-propagation
- cost-benefit/workflow-cost
- cost-benefit/risk-analysis
- frontmatter/parsing
- frontmatter/file-io-and-serialize
- schema/numeric-methods-and-defaults
scope: narrow
risk: low
impact: project
level: implementation
---
## Description
Wire up `src/index.ts` to re-export the full public API surface. This is the main entry point for consumers: everything they need should be importable from `@alkdev/taskgraph`.
## Acceptance Criteria
- [ ] `src/index.ts` re-exports all public API items:
- `TaskGraph` class (from `src/graph/index.ts`)
- All analysis functions: `parallelGroups`, `criticalPath`, `weightedCriticalPath`, `bottlenecks`, `riskPath`, `riskDistribution`, `shouldDecomposeTask`, `workflowCost`, `calculateTaskEv`
- All categorical numeric functions: `scopeCostEstimate`, `scopeTokenEstimate`, `riskSuccessProbability`, `riskWeight`, `impactWeight`, `resolveDefaults`
- All frontmatter functions: `parseFrontmatter`, `parseTaskFile`, `parseTaskDirectory`, `serializeFrontmatter`
- All schemas and types: all enum schemas, `TaskInput`, `DependencyEdge`, `TaskGraphNodeAttributes`, `TaskGraphEdgeAttributes`, `TaskGraphSerialized`, all result types
- All error classes: `TaskgraphError`, `TaskNotFoundError`, `CircularDependencyError`, `InvalidInputError`, `DuplicateNodeError`, `DuplicateEdgeError`
- [ ] No internal implementation details leak through the public API
- [ ] `package.json` `"exports"` field configured for ESM primary + CJS compat
- [ ] TypeScript declarations (`tsc --emitDeclarationOnly`) verify the public surface compiles correctly
- [ ] Consumer import `import { TaskGraph, workflowCost } from "@alkdev/taskgraph"` works
## References
- docs/architecture/api-surface.md — full public API
- docs/architecture/build-distribution.md — package name, ESM primary
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

View File

@@ -0,0 +1,51 @@
---
id: cost-benefit/dag-propagation
name: Implement DAG-propagation effective probability computation
status: pending
depends_on:
- cost-benefit/ev-calculation
- graph/queries
- schema/numeric-methods-and-defaults
scope: moderate
risk: high
impact: phase
level: implementation
---
## Description
Implement the DAG-propagation cost model in `src/analysis/cost-benefit.ts`. This is the core algorithmic contribution beyond the Rust CLI — it captures the structural reality that upstream failures multiply downstream damage.
Per [cost-benefit.md](../../../docs/architecture/cost-benefit.md), the algorithm:
1. Processes tasks in topological order
2. For each task with prerequisites, computes `pEffective` from intrinsic probability + upstream propagation
3. Upstream propagation: `parentP + (1 - parentP) × qualityRetention` for each parent
4. `pEffective` = intrinsic × product of all inherited quality factors
## Acceptance Criteria
- [ ] `computeEffectiveP(taskId, graph, upstreamSuccessProbs, defaultQualityRetention, propagationMode)` — internal helper
- [ ] In `dag-propagate` mode: for each task in topological order:
- Get intrinsic probability from `resolveDefaults(risk).successProbability`
- For each prerequisite, compute inherited quality: `parentP + (1 - parentP) × qualityRetention`
- `pEffective` = intrinsic × product of all inherited quality factors
- Store task's **actual** success probability for downstream propagation (use `pEffective` if this is the task's real probability)
- [ ] In `independent` mode: `pEffective = pIntrinsic` (no propagation)
- [ ] Completed tasks (`status: "completed"`): propagate with `p = 1.0` when `includeCompleted: false`
- [ ] `qualityRetention` per edge defaults to 0.9, can be overridden per-edge via `defaultQualityRetention` option or edge attributes
- [ ] Throws `CircularDependencyError` if graph is cyclic (needs topo sort)
- [ ] Unit tests: simple chain (verify compounding effect), diamond graph, independent vs dag-propagate comparison matches Python research model results, completed task exclusion/propagation semantics
## References
- docs/architecture/cost-benefit.md — DAG-propagation algorithm, qualityRetention semantics
- docs/architecture/decisions/004-workflow-cost-dag-propagation.md — ADR-004
- docs/architecture/decisions/005-no-depth-escalation-v1.md — no depth escalation in v1
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

View File

@@ -0,0 +1,48 @@
---
id: cost-benefit/ev-calculation
name: Implement calculateTaskEv pure function
status: pending
depends_on:
- schema/numeric-methods-and-defaults
- schema/result-types
scope: narrow
risk: low
impact: component
level: implementation
---
## Description
Implement `calculateTaskEv(p, scopeCost, impactWeight, config?): EvResult` in `src/analysis/cost-benefit.ts`. This is the pure math function — takes numeric inputs, returns EV result. No graph dependency.
Per [cost-benefit.md](../../../docs/architecture/cost-benefit.md):
```
EV = P_success × C_success + (1 - P_success) × C_fail
```
Where `C_fail = scopeCost + fallbackCost + timeLost × expectedRetries`.
## Acceptance Criteria
- [ ] `calculateTaskEv(p: number, scopeCost: number, impactWeight: number, config?: EvConfig): EvResult`
- [ ] Returns `EvResult`: `{ ev, pSuccess, expectedRetries }`
- [ ] `expectedRetries` = `(1 - p) / p` when `p > 0`, else 0 (geometric series)
- [ ] `C_fail = scopeCost * impactWeight + fallbackCost + timeLost * expectedRetries` (impactWeight scales the cost)
- [ ] `EV = p * scopeCost * impactWeight + (1-p) * C_fail`
- [ ] When `config.retries` is provided and > 0, caps `expectedRetries` at `retries`
- [ ] When `config.valueRate` is non-zero, multiplies the final EV
- [ ] Edge cases: `p = 0` (guaranteed failure), `p = 1` (guaranteed success), default config (all zeros)
- [ ] Unit tests: known calculations from the Python research model, boundary values, config variations
## References
- docs/architecture/cost-benefit.md — EV formula, EvConfig parameters, EvResult
- docs/architecture/api-surface.md — calculateTaskEv signature
- docs/architecture/schemas.md — EvConfig, EvResult schemas
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

View File

@@ -0,0 +1,50 @@
---
id: cost-benefit/risk-analysis
name: Implement riskPath, riskDistribution, and shouldDecomposeTask functions
status: pending
depends_on:
- cost-benefit/ev-calculation
- analysis/critical-path
- schema/numeric-methods-and-defaults
scope: moderate
risk: low
impact: component
level: implementation
---
## Description
Implement the three risk analysis functions: `riskPath`, `riskDistribution`, and `shouldDecomposeTask`. These are standalone composable functions that serve different analysis use cases.
## Acceptance Criteria
- [ ] `riskPath(graph: TaskGraph): RiskPathResult`:
- Calls `weightedCriticalPath` with weight function `riskWeight * impactWeight`
- Returns `{ path: string[], totalRisk: number }`
- `totalRisk` is the sum of weight values along the path
- [ ] `riskDistribution(graph: TaskGraph): RiskDistributionResult`:
- Groups all tasks by their `risk` attribute
- Returns `{ trivial: string[], low: string[], medium: string[], high: string[], critical: string[], unspecified: string[] }`
- Tasks with `risk: undefined` (not assessed) go in `unspecified`
- No duplicate task IDs in any bucket
- [ ] `shouldDecomposeTask(attrs: TaskGraphNodeAttributes): DecomposeResult`:
- Pure function — takes node attributes (not a graph)
- Internally calls `resolveDefaults` for `risk` and `scope` (nullable fields)
- Flags decomposition when: risk >= "high" OR scope >= "broad"
- Returns `{ shouldDecompose: boolean, reasons: string[] }`
- Unassessed tasks (null/undefined risk or scope) are never flagged — default values are below threshold
- Provides specific reasons: e.g., `"risk: high — failure probability 0.35"`, `"scope: broad — cost estimate 4.0"`
- [ ] Unit tests for all three functions with known inputs/outputs
## References
- docs/architecture/api-surface.md — risk analysis functions
- docs/architecture/cost-benefit.md — riskPath, riskDistribution, shouldDecomposeTask, decomposition threshold
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

View File

@@ -0,0 +1,45 @@
---
id: cost-benefit/workflow-cost
name: Implement workflowCost orchestration function
status: pending
depends_on:
- cost-benefit/dag-propagation
- graph/queries
scope: moderate
risk: medium
impact: component
level: implementation
---
## Description
Implement `workflowCost(graph: TaskGraph, options?: WorkflowCostOptions): WorkflowCostResult` in `src/analysis/cost-benefit.ts`. This orchestrates the per-task EV calculations, handles DAG propagation, and enriches results with `taskId` and `name` from the graph.
## Acceptance Criteria
- [ ] `workflowCost` accepts `WorkflowCostOptions` with optional: `includeCompleted`, `limit`, `propagationMode`, `defaultQualityRetention`
- [ ] Default propagation mode: `"dag-propagate"` per ADR-004
- [ ] Default `defaultQualityRetention`: 0.9
- [ ] Each task in result includes: `taskId`, `name`, `ev`, `pIntrinsic`, `pEffective`, `probability` (= `pEffective`), `scopeCost`, `impactWeight`
- [ ] `totalEv`: sum of all task EVs (excluding completed tasks from output when `includeCompleted: false`)
- [ ] `averageEv`: `totalEv / tasks.length`
- [ ] `propagationMode`: reflected in result
- [ ] When `includeCompleted: false`: completed tasks excluded from `tasks` array but remain in propagation chain with p=1.0
- [ ] When `includeCompleted: false`: only `"completed"` status triggers exclusion; `"failed"` and `"blocked"` are always included
- [ ] When `limit` is set: returns at most `limit` tasks (sorted by EV descending? or topological order? spec says "limits the number of tasks in the result" — use topological order with limit)
- [ ] Throws `CircularDependencyError` if graph is cyclic
- [ ] Unit tests: full workflow cost calculation, independent vs dag-propagate comparison, excludeCompleted scenarios, limit behavior
## References
- docs/architecture/cost-benefit.md — workflow cost, skip-completed semantics
- docs/architecture/api-surface.md — workflowCost signature, WorkflowCostOptions
- docs/architecture/decisions/004-workflow-cost-dag-propagation.md — ADR-004
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

View File

@@ -0,0 +1,43 @@
---
id: error/error-hierarchy
name: Implement typed error class hierarchy
status: pending
depends_on:
- setup/project-init
scope: narrow
risk: trivial
impact: phase
level: implementation
---
## Description
Implement the custom error classes in `src/error/index.ts` per [errors-validation.md](../../../docs/architecture/errors-validation.md). All errors extend `TaskgraphError` which extends `Error`. Each subclass adds typed fields for programmatic error recovery.
## Acceptance Criteria
- [ ] `src/error/index.ts` exports:
- `TaskgraphError extends Error` — base class
- `TaskNotFoundError extends TaskgraphError` with `taskId: string` field
- `CircularDependencyError extends TaskgraphError` with `cycles: string[][]` field
- `InvalidInputError extends TaskgraphError` with `field: string` and `message: string` fields
- `DuplicateNodeError extends TaskgraphError` with `taskId: string` field
- `DuplicateEdgeError extends TaskgraphError` with `prerequisite: string` and `dependent: string` fields
- [ ] Each error class sets `this.name` to the class name
- [ ] Each error class properly extends the prototype chain (`Object.setPrototypeOf(this, new.target.prototype)`)
- [ ] `InvalidInputError` supports construction from TypeBox `Value.Errors()` output (receives structured field/path/value info)
- [ ] `CircularDependencyError` receives `string[][]` where each inner array is an ordered cycle path
- [ ] Unit tests verifying: correct `instanceof` chain, field access, `.name` property, error messages
## References
- docs/architecture/errors-validation.md — error types, when each is thrown
- docs/architecture/api-surface.md — error documentation on specific methods
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

View File

@@ -0,0 +1,54 @@
---
id: frontmatter/file-io-and-serialize
name: Implement parseTaskFile, parseTaskDirectory, and serializeFrontmatter
status: pending
depends_on:
- frontmatter/parsing
- schema/input-schemas
scope: moderate
risk: low
impact: component
level: implementation
---
## Description
Implement the file I/O frontmatter functions and the serializer. These are convenience wrappers for common use cases and the reverse operation (TaskInput → markdown).
Per [frontmatter.md](../../../docs/architecture/frontmatter.md):
- `parseTaskFile`/`parseTaskDirectory` depend on `node:fs/promises` (Node.js only)
- `parseFrontmatter` is runtime-agnostic
- `serializeFrontmatter` uses `yaml.stringify()` for the data portion
## Acceptance Criteria
- [ ] `parseTaskFile(filePath: string): Promise<TaskInput>`:
- Reads file using `node:fs/promises.readFile`
- Delegates to `parseFrontmatter` for parsing and validation
- Throws underlying Node.js error for I/O failures (ENOENT, EACCES, etc.)
- [ ] `parseTaskDirectory(dirPath: string): Promise<TaskInput[]>`:
- Recursive directory scanning via `node:fs/promises.readdir` with `{ recursive: true }` or manual recursion
- Filters for `.md` files only
- Silently skips files without valid `---`-delimited frontmatter (no error thrown, just omitted from results)
- Throws underlying Node.js error for I/O failures
- Uses `parseTaskFile` per file
- [ ] `serializeFrontmatter(task: TaskInput, body?: string): string`:
- Constructs `---`-delimited markdown output
- Uses `yaml.stringify()` for the `TaskInput` data (excludes `id` from frontmatter? No — per Rust CLI convention, `id` comes from the filename, but in the schema it's part of `TaskInput`. Follow schema: include all `TaskInput` fields in the YAML.)
- Appends body content (default: empty string) after closing `---`
- Handles nullable fields correctly: `risk: null``risk: null` in YAML (explicit null), absent fields → omitted from YAML
- [ ] File I/O functions documented as Node.js-only in JSDoc comments
- [ ] Unit tests: parseTaskFile with temp file, parseTaskDirectory with temp dir (including non-.md files, missing frontmatter files), serializeFrontmatter round-trip parseFrontmatter(serializeFrontmatter(task)) ≈ task
## References
- docs/architecture/frontmatter.md — file I/O functions, splitter, serializer
- docs/architecture/schemas.md — TaskInput definition for serialization
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

View File

@@ -0,0 +1,51 @@
---
id: frontmatter/parsing
name: Implement parseFrontmatter with YAML parsing and TypeBox validation
status: pending
depends_on:
- frontmatter/splitter
- schema/input-schemas
- error/error-hierarchy
scope: narrow
risk: low
impact: component
level: implementation
---
## Description
Implement `parseFrontmatter(markdown: string): TaskInput` in `src/frontmatter/parse.ts`. This combines the splitter with `yaml.parse()` and TypeBox validation. Invalid frontmatter throws `InvalidInputError` with field-level details.
Per [frontmatter.md](../../../docs/architecture/frontmatter.md), the function uses:
- The custom splitter for `---` extraction
- `yaml.parse()` (from `yaml` package, zero dependencies) for YAML↔JS conversion
- TypeBox `Value.Check()` + `Value.Errors()` for structured field-level validation
- `Value.Clean()` to strip unknown properties from untrusted input
## Acceptance Criteria
- [ ] `parseFrontmatter(markdown: string): TaskInput`:
- Calls splitter to extract YAML string
- Throws `InvalidInputError` if no valid frontmatter found (not `null` return — the caller expects TaskInput)
- Calls `yaml.parse(yamlString)` for YAML 1.2 parsing
- Runs `Value.Clean(TaskInput, parsed)` to strip unknown properties
- Runs `Value.Check(TaskInput, cleaned)` — if fails, runs `Value.Errors()` and throws `InvalidInputError` with structured field/path/message/value details
- Returns validated `TaskInput`
- [ ] `InvalidInputError` is populated with field-level details from `Value.Errors()` output
- [ ] YAML 1.2 used exclusively (the `yaml` package default) — no YAML 1.1 type coercion
- [ ] Handles YAML `null` values (e.g., `risk:` with no value) correctly — becomes `null` in the TaskInput (distinction from absent field)
- [ ] Unit tests: valid frontmatter, missing required fields, invalid enum values, unknown fields stripped, null categorical values preserved
## References
- docs/architecture/frontmatter.md — parseFrontmatter, yaml package, no gray-matter
- docs/architecture/schemas.md — TaskInput schema, Nullable helper
- docs/architecture/errors-validation.md — InvalidInputError
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

View File

@@ -0,0 +1,45 @@
---
id: frontmatter/splitter
name: Implement frontmatter delimiter splitter (~40 lines)
status: pending
depends_on:
- setup/project-init
scope: single
risk: trivial
impact: component
level: implementation
---
## Description
Implement the self-contained `---` delimited frontmatter splitter in `src/frontmatter/parse.ts`. This is a ~40 line function that extracts the YAML data string and markdown content body from a markdown string. No gray-matter dependency.
Per [frontmatter.md](../../../docs/architecture/frontmatter.md), the splitter:
1. Checks for opening `---` delimiter (not `----`)
2. Finds closing `\n---` delimiter
3. Extracts YAML data string and markdown content body
4. Returns `{ data: string, content: string }` or `null` if no valid frontmatter
## Acceptance Criteria
- [ ] `splitFrontmatter(markdown: string): { data: string; content: string } | null`
- [ ] Opening `---` must be at the start of the file (or after optional BOM/whitespace on first line)
- [ ] `----` (4+ dashes) is NOT a valid delimiter — only exact `---`
- [ ] Closing delimiter requires `\n---` (newline before dashes)
- [ ] Returns `null` if no valid frontmatter found
- [ ] Returns `{ data: "", content: "" }` if frontmatter is present but empty (e.g., `---\n---`)
- [ ] Content body starts after the closing `---` + newline
- [ ] Handles edge cases: no closing delimiter (returns null), file with only `---\n---`, file with no `---` at all
- [ ] Unit tests: standard frontmatter, no frontmatter, empty frontmatter, multi-line content, dashes in content body (shouldn't be treated as delimiters), 4+ dashes ignored
## References
- docs/architecture/frontmatter.md — splitter design, supply chain decision
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

View File

@@ -0,0 +1,62 @@
---
id: graph/construction
name: Implement TaskGraph construction methods (fromTasks, fromRecords, fromJSON, addTask, addDependency)
status: pending
depends_on:
- graph/taskgraph-class
scope: broad
risk: high
impact: phase
level: implementation
---
## Description
Implement the four construction methods in `src/graph/construction.ts` and integrate them as static methods on `TaskGraph`. These are the primary ways to create a graph instance from structured data. Each method has distinct semantics for edge handling, error behavior, and validation.
Per [graph-model.md](../../../docs/architecture/graph-model.md), the preferred internal approach is to build a serialized graph JSON blob and call `graph.import()` for paths 1 and 2 (better performance than N individual addNode/addEdge calls).
## Acceptance Criteria
- [ ] `TaskGraph.fromTasks(tasks: TaskInput[]): TaskGraph`:
- Transforms `TaskInput[]` into node data + edge data, builds serialized blob, calls `graph.import()`
- Each `dependsOn` entry creates an edge with default `qualityRetention: 0.9`
- `dependsOn` targets not matching any task ID become orphan nodes with default attributes
- Duplicate task IDs throw `DuplicateNodeError`
- Uses `mergeNode` for idempotent node merging (same ID gets merged attributes)
- Duplicate `dependsOn` entries for the same pair create only one edge (idempotent via `addEdgeWithKey`)
- [ ] `TaskGraph.fromRecords(tasks: TaskInput[], edges: DependencyEdge[]): TaskGraph`:
- Edges must reference tasks that exist in the `tasks` array — throws `TaskNotFoundError` for dangling references
- Per-edge `qualityRetention` from the `DependencyEdge` objects
- Duplicate task IDs throw `DuplicateNodeError`
- Duplicate edges (same prerequisite→dependent pair) throw `DuplicateEdgeError`
- [ ] `TaskGraph.fromJSON(data: TaskGraphSerialized): TaskGraph`:
- Validates input against `TaskGraphSerialized` schema (using TypeBox `Value.Check`)
- Uses `graph.import()` on the validated data
- Orphan nodes in JSON are preserved
- [ ] `addTask(id: string, attributes: TaskGraphNodeAttributes): void`:
- Throws `DuplicateNodeError` if ID already exists
- Adds node to internal graphology instance
- [ ] `addDependency(prerequisite: string, dependent: string, qualityRetention?: number): void`:
- Throws `TaskNotFoundError` if either endpoint doesn't exist
- Throws `DuplicateEdgeError` if edge already exists
- Uses `addEdgeWithKey` with deterministic key `${prerequisite}->${dependent}`
- Default `qualityRetention: 0.9` if not provided
- [ ] `fromTasks`/`fromRecords` strip `null``undefined` for categorical fields during `TaskInput``TaskGraphNodeAttributes` transformation
- [ ] `TaskInput` fields `tags`, `assignee`, `due`, `created`, `modified` are not stored on graph nodes (belong to caller)
- [ ] Unit tests for each construction method: happy path, error cases, edge cases (empty arrays, cycles not rejected at construction time)
- [ ] All construction methods use deterministic edge keys per ADR-006
## References
- docs/architecture/graph-model.md — construction paths, TaskInput→attributes transformation, error handling table
- docs/architecture/api-surface.md — TaskGraph class, fromTasks/fromRecords/fromJSON/addTask/addDependency
- docs/architecture/decisions/006-deterministic-edge-keys.md — deterministic edge keys
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

View File

@@ -0,0 +1,36 @@
---
id: graph/export
name: Implement TaskGraph export methods (export, toJSON)
status: pending
depends_on:
- graph/taskgraph-class
scope: single
risk: trivial
impact: component
level: implementation
---
## Description
Implement the `export()` and `toJSON()` methods on `TaskGraph`. These wrap graphology's `export()` to produce `TaskGraphSerialized` output.
## Acceptance Criteria
- [ ] `export(): TaskGraphSerialized` — wraps `graph.export()` and validates the output conforms to the `TaskGraphSerialized` schema
- [ ] `toJSON(): TaskGraphSerialized` — alias for `export()` (enables `JSON.stringify(graph)` to work)
- [ ] Exported data includes all node attributes and edge attributes (including `qualityRetention`)
- [ ] Round-trip: `TaskGraph.fromJSON(graph.export())` produces an equivalent graph
- [ ] Unit test: create graph, add tasks/edges, export, round-trip through fromJSON, verify equivalence
## References
- docs/architecture/api-surface.md — export/toJSON methods
- docs/architecture/schemas.md — TaskGraphSerialized schema
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

View File

@@ -0,0 +1,38 @@
---
id: graph/mutation
name: Implement TaskGraph mutation methods (remove, update, updateEdgeAttributes)
status: pending
depends_on:
- graph/taskgraph-class
scope: narrow
risk: low
impact: component
level: implementation
---
## Description
Implement mutation methods in `src/graph/mutation.ts` and integrate on `TaskGraph`. These methods modify the graph in-place.
## Acceptance Criteria
- [ ] `removeTask(id: string): void` — No-op if node doesn't exist. Removes node and cascades edge removal (graphology handles this automatically).
- [ ] `removeDependency(prerequisite: string, dependent: string): void` — No-op if edge doesn't exist. Uses deterministic edge key `${prerequisite}->${dependent}` to identify the edge.
- [ ] `updateTask(id: string, attributes: Partial<TaskGraphNodeAttributes>): void` — Throws `TaskNotFoundError` if ID doesn't exist. Uses `mergeNodeAttributes` for shallow merge of provided attributes.
- [ ] `updateEdgeAttributes(prerequisite: string, dependent: string, attrs: Partial<TaskGraphEdgeAttributes>): void` — Throws `TaskNotFoundError` (actually `TaskNotFoundError` for the edge itself, but per the spec, edge attributes need both endpoints to exist) if the edge doesn't exist. Uses `mergeEdgeAttributes` for shallow merge.
- [ ] All mutations maintain the deterministic edge key format
- [ ] Unit tests: remove nonexistent node/edge is no-op, update nonexistent throws, partial updates merge correctly
## References
- docs/architecture/api-surface.md — mutation methods
- docs/architecture/errors-validation.md — mutation operation behavior table
- docs/architecture/graph-model.md — edge attributes, mutation semantics
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

View File

@@ -0,0 +1,53 @@
---
id: graph/queries
name: Implement TaskGraph query methods (hasCycles, findCycles, topologicalOrder, dependencies, dependents, taskCount, getTask)
status: pending
depends_on:
- graph/taskgraph-class
scope: moderate
risk: medium
impact: component
level: implementation
---
## Description
Implement query methods in `src/graph/queries.ts` and integrate on `TaskGraph`. The `findCycles` implementation requires a custom 3-color DFS since `graphology-components` only gives SCCs, not cycle paths.
Per [errors-validation.md](../../../docs/architecture/errors-validation.md):
- `hasCycles()` returns boolean (uses `graphology-dag` or `graphology-components` for fast check)
- `findCycles()` returns `string[][]` — each inner array is an ordered cycle path
- `topologicalOrder()` throws `CircularDependencyError` with `cycles` populated when graph is cyclic (per ADR-003)
## Acceptance Criteria
- [ ] `hasCycles(): boolean` — uses `graphology-dag.hasCycle()` or `graphology-components` SCC check as fast pre-check
- [ ] `findCycles(): string[][]`:
- Uses `stronglyConnectedComponents()` as pre-check: if zero multi-node SCCs and no self-loops, skip DFS
- Custom 3-color DFS (WHITE/GREY/BLACK) to extract cycle paths
- Returns one representative cycle per back edge, not exhaustive enumeration
- Each inner array is an ordered node sequence where last node has edge back to first
- [ ] `topologicalOrder(): string[]`:
- Uses `graphology-dag.topologicalSort()` for the actual sort
- **Throws `CircularDependencyError`** (with `cycles` from `findCycles()`) when graph is cyclic
- Returns `string[]` of task IDs in prerequisite→dependent order
- [ ] `dependencies(taskId: string): string[]` — returns prerequisite task IDs (inNeighbors). Throws `TaskNotFoundError` if ID doesn't exist.
- [ ] `dependents(taskId: string): string[]` — returns dependent task IDs (outNeighbors). Throws `TaskNotFoundError` if ID doesn't exist.
- [ ] `taskCount(): number` — returns number of nodes
- [ ] `getTask(taskId: string): TaskGraphNodeAttributes | undefined` — returns node attributes or undefined
- [ ] Unit tests: cycle detection on known cyclic/acyclic graphs, topologicalOrder on DAG, topologicalOrder throws on cyclic graph, dependency/dependent queries
## References
- docs/architecture/api-surface.md — query methods
- docs/architecture/errors-validation.md — cycle handling, CircularDependencyError
- docs/architecture/cost-benefit.md — findCycles algorithm description
- docs/architecture/decisions/003-topo-order-throws-on-cycle.md — ADR-003
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

View File

@@ -0,0 +1,54 @@
---
id: graph/subgraph-and-validation
name: Implement TaskGraph subgraph and validation methods
status: pending
depends_on:
- graph/taskgraph-class
- graph/queries
- schema/input-schemas
- schema/graph-schemas
scope: narrow
risk: low
impact: component
level: implementation
---
## Description
Implement the `subgraph()` method and the three validation methods (`validateSchema`, `validateGraph`, `validate`) on `TaskGraph`.
Per [ADR-007](../../../docs/architecture/decisions/007-subgraph-internal-only.md), `subgraph` returns only edges where both endpoints are in the filtered set.
Per [errors-validation.md](../../../docs/architecture/errors-validation.md), validation methods collect issues and return arrays — never throw.
## Acceptance Criteria
- [ ] `subgraph(filter: (taskId: string, attrs: TaskGraphNodeAttributes) => boolean): TaskGraph`:
- Uses `graphology-operators.subgraph` to extract matching nodes
- Returns only edges where both endpoints are in the filtered set (internal-only) per ADR-007
- Returns a new `TaskGraph` instance (not mutating the original)
- [ ] `validateSchema(): ValidationError[]`:
- Uses TypeBox `Value.Check()` and `Value.Errors()` on each node's attributes
- Returns structured `ValidationError[]` with `type: "schema"`, `taskId`, `field`, `message`, `value`
- [ ] `validateGraph(): GraphValidationError[]`:
- Runs `findCycles()` and checks for dangling dependency references
- Returns structured `GraphValidationError[]` with `type: "graph"`, `category`, `message`, optional `details`
- Cycle category: `"cycle"` with cycle paths in `details`
- Dangling reference category: `"dangling-reference"` with the referencing task ID
- [ ] `validate(): ValidationError[]` — runs both `validateSchema()` and `validateGraph()`, returns combined array
- [ ] `ValidationError` and `GraphValidationError` interfaces defined (may be in error module or co-located)
- [ ] Unit tests: subgraph filtering, subgraph excludes external edges, validateSchema catches invalid enums, validateGraph catches cycles and dangling refs
## References
- docs/architecture/api-surface.md — validation API, subgraph
- docs/architecture/errors-validation.md — validation levels, return types
- docs/architecture/decisions/007-subgraph-internal-only.md — subgraph semantics
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

View File

@@ -0,0 +1,45 @@
---
id: graph/taskgraph-class
name: Implement TaskGraph class skeleton with graphology DirectedGraph
status: pending
depends_on:
- schema/enums
- schema/input-schemas
- schema/graph-schemas
- error/error-hierarchy
- setup/project-init
scope: moderate
risk: medium
impact: phase
level: implementation
---
## Description
Create the `TaskGraph` class in `src/graph/index.ts` that wraps `graphology.DirectedGraph`. This is the data class that holds the graph instance and provides the foundation for construction, mutation, and query methods. At this stage, implement the constructor, `raw` getter, and the overall class structure. Actual construction and analysis methods come in dependent tasks.
## Acceptance Criteria
- [ ] `src/graph/index.ts` exports `TaskGraph` class
- [ ] Constructor creates an internal `graphology.DirectedGraph` with options `{ type: 'directed', multi: false, allowSelfLoops: false }`
- [ ] `get raw(): Graph` returns the underlying graphology instance
- [ ] Constructor accepts optional `TaskGraphSerialized` for initializing from serialized data (delegates to `fromJSON` pattern)
- [ ] Class stores edge key format: `${source}->${target}` (per ADR-006)
- [ ] No parallel edges constraint enforced by `multi: false` graph option
- [ ] No self-loops constraint enforced by `allowSelfLoops: false` graph option
- [ ] Internal `_edgeKey(source, target): string` method producing deterministic keys
- [ ] Re-exported from `src/index.ts`
## References
- docs/architecture/api-surface.md — TaskGraph class API
- docs/architecture/graph-model.md — construction paths, edge direction, constraints
- docs/architecture/decisions/006-deterministic-edge-keys.md — edge key format
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

View File

@@ -0,0 +1,48 @@
---
id: review/complete-library
name: Final review — validate full library against architecture docs
status: pending
depends_on:
- api/public-exports
- review/graph-complete
- frontmatter/file-io-and-serialize
- cost-benefit/workflow-cost
- cost-benefit/risk-analysis
scope: broad
risk: low
impact: project
level: review
---
## Description
Final review of the complete library. Verify the full API surface matches architecture docs, all analysis functions produce correct results, and the library achieves its stated purpose: pure TypeScript task graph library with graphology, replicating and extending the essential graph algorithms and cost-benefit math from the Rust CLI.
## Acceptance Criteria
- [ ] Public API matches [api-surface.md](../../../docs/architecture/api-surface.md) exactly — no missing exports, no extra exports
- [ ] All construction paths work: fromTasks, fromRecords, fromJSON, incremental
- [ ] DAG-propagation cost model produces results consistent with Python research model examples
- [ ] Independent model available as degenerate case (set `propagationMode: 'independent'` or `defaultQualityRetention: 1.0`)
- [ ] Frontmatter parsing round-trips correctly: `parseFrontmatter(serializeFrontmatter(task))` ≈ task
- [ ] `Value.Clean()` and `Value.Errors()` used correctly throughout (no `Value.Assert()` where structured errors needed)
- [ ] No gray-matter, no js-yaml, no Zod anywhere in the dependency tree
- [ ] `npm pack` produces a valid package with correct exports
- [ ] All tests pass: `npm test`
- [ ] TypeScript strict mode compilation succeeds with no errors
- [ ] Build output (`dist/`) is correct: ESM + CJS + declarations
## References
- docs/architecture/README.md
- docs/architecture/api-surface.md
- docs/architecture/build-distribution.md
- docs/architecture/cost-benefit.md
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

View File

@@ -0,0 +1,53 @@
---
id: review/graph-complete
name: Review TaskGraph class implementation for correctness and API compliance
status: pending
depends_on:
- graph/construction
- graph/mutation
- graph/queries
- graph/subgraph-and-validation
- graph/export
- review/schemas-and-errors
scope: moderate
risk: medium
impact: phase
level: review
---
## Description
Review the TaskGraph class implementation before building analysis functions on top of it. The graph layer is the foundation for all analysis — incorrect behavior here propagates everywhere.
## Acceptance Criteria
- [ ] Construction methods match [graph-model.md](../../../docs/architecture/graph-model.md) error handling table exactly:
- `fromTasks`: silent orphan nodes, `DuplicateNodeError`, idempotent duplicate edges
- `fromRecords`: `TaskNotFoundError` for dangling edges, `DuplicateNodeError`, `DuplicateEdgeError`
- `fromJSON`: validated against schema, orphans preserved
- `addTask`: `DuplicateNodeError`
- `addDependency`: `TaskNotFoundError`, `DuplicateEdgeError`
- [ ] Edge direction is prerequisite→dependent throughout (matches Rust CLI convention)
- [ ] Deterministic edge keys `${source}->${target}` used via `addEdgeWithKey` (ADR-006)
- [ ] `topologicalOrder` throws `CircularDependencyError` with `cycles` populated (ADR-003)
- [ ] `findCycles` returns actual cycle paths (not just SCCs)
- [ ] `subgraph` returns internal-only edges (ADR-007)
- [ ] Validation methods return arrays, never throw
- [ ] Mutation error semantics match [errors-validation.md](../../../docs/architecture/errors-validation.md) table (no-op for remove, throws for update on nonexistent)
- [ ] `export()`/`toJSON()` round-trips correctly
- [ ] `raw` getter exposed, warning about direct mutation documented in code comments
- [ ] All tests pass, including edge cases (empty graphs, single-node, cyclic graphs, disconnected components)
## References
- docs/architecture/graph-model.md
- docs/architecture/api-surface.md
- docs/architecture/errors-validation.md
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

View File

@@ -0,0 +1,49 @@
---
id: review/schemas-and-errors
name: Review schema, enum, and error implementations for consistency
status: pending
depends_on:
- schema/enums
- schema/input-schemas
- schema/graph-schemas
- schema/result-types
- schema/numeric-methods-and-defaults
- error/error-hierarchy
scope: narrow
risk: low
impact: phase
level: review
---
## Description
Review the schema and error layer implementations before building the graph and analysis layers on top. This is a critical checkpoint because everything downstream depends on these types being correct and consistent with the architecture docs.
## Acceptance Criteria
- [ ] All TypeBox schemas match [schemas.md](../../../docs/architecture/schemas.md) exactly
- [ ] All `Static<typeof>` type aliases correctly derived — no manual type definitions
- [ ] Nullable helper used consistently in TaskInput (not in TaskGraphNodeAttributes)
- [ ] Enum values match DB/frontmatter conventions exactly
- [ ] Numeric method tables match spec tables exactly
- [ ] `resolveDefaults` correctly separates "nullable categorical→default" from "label-only nullable→stays nullable"
- [ ] Error class hierarchy is correct: all extend TaskgraphError, all have proper `name` and typed fields
- [ ] `InvalidInputError` can be constructed from `Value.Errors()` output
- [ ] `CircularDependencyError.cycles` type is `string[][]`
- [ ] No Zod, no gray-matter, no js-yaml in any dependency
- [ ] `package.json` lists only approved dependencies
- [ ] All tests pass
## References
- docs/architecture/schemas.md
- docs/architecture/errors-validation.md
- docs/architecture/frontmatter.md — supply chain constraints
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

View File

@@ -0,0 +1,44 @@
---
id: schema/enums
name: Define TypeBox categorical enum schemas and type aliases
status: pending
depends_on:
- setup/project-init
scope: narrow
risk: trivial
impact: component
level: implementation
---
## Description
Define all categorical enum schemas using `Type.Union([Type.Literal(...)])` pattern per [schemas.md](../../../docs/architecture/schemas.md). Each enum gets a schema constant (PascalCase + `Enum` suffix) and a `Static<typeof>` type alias (PascalCase, no suffix).
The six enums: `TaskScopeEnum`, `TaskRiskEnum`, `TaskImpactEnum`, `TaskLevelEnum`, `TaskPriorityEnum`, `TaskStatusEnum`.
## Acceptance Criteria
- [ ] `src/schema/enums.ts` exports all six enum schemas and their type aliases
- [ ] Each enum uses `Type.Union([Type.Literal("value"), ...])` pattern per [typebox-patterns.md](../../../docs/research/typebox-patterns.md)
- [ ] `TaskScopeEnum`: `"single" | "narrow" | "moderate" | "broad" | "system"`
- [ ] `TaskRiskEnum`: `"trivial" | "low" | "medium" | "high" | "critical"`
- [ ] `TaskImpactEnum`: `"isolated" | "component" | "phase" | "project"`
- [ ] `TaskLevelEnum`: `"planning" | "decomposition" | "implementation" | "review" | "research"`
- [ ] `TaskPriorityEnum`: `"low" | "medium" | "high" | "critical"`
- [ ] `TaskStatusEnum`: `"pending" | "in-progress" | "completed" | "failed" | "blocked"`
- [ ] Type aliases derived via `Static<typeof>`: `TaskScope`, `TaskRisk`, `TaskImpact`, `TaskLevel`, `TaskPriority`, `TaskStatus`
- [ ] Naming convention matches spec: `Enum` suffix on schema constants only, never on type aliases
- [ ] `src/schema/index.ts` re-exports all schemas and types
## References
- docs/architecture/schemas.md — enum definitions, naming convention
- docs/research/typebox-patterns.md — TypeBox enum patterns, naming convention
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

View File

@@ -0,0 +1,42 @@
---
id: schema/graph-schemas
name: Define TaskGraphNodeAttributes, TaskGraphEdgeAttributes, and SerializedGraph
status: pending
depends_on:
- schema/enums
scope: narrow
risk: low
impact: component
level: implementation
---
## Description
Define graph attribute schemas and the serialized graph generic in `src/schema/graph.ts`. `TaskGraphNodeAttributes` carries only analysis-relevant metadata (no tags, assignee, due, etc.). `SerializedGraph` is a generic factory parameterized with node and edge attribute types.
## Acceptance Criteria
- [ ] `src/schema/graph.ts` exports:
- `TaskGraphNodeAttributes` schema: `name: Type.String()`, optional categorical enums (scope, risk, impact, level, priority, status) — **not** nullable on the graph (absent = not stored)
- `type TaskGraphNodeAttributes` derived
- `TaskGraphNodeAttributesUpdate = Type.Partial(TaskGraphNodeAttributes)` and type alias
- `TaskGraphEdgeAttributes` schema: `qualityRetention: Type.Optional(Type.Number())`
- `type TaskGraphEdgeAttributes` derived
- `SerializedGraph` generic factory parameterized with `<N extends TSchema, E extends TSchema, G extends TSchema>`
- `TaskGraphSerialized = SerializedGraph(TaskGraphNodeAttributes, TaskGraphEdgeAttributes, Type.Object({}))` and type alias
- [ ] `SerializedGraph` generic follows graphology JSON format: `attributes`, `options: { type: "directed", multi: false, allowSelfLoops: false }`, `nodes: [{ key, attributes }]`, `edges: [{ key, source, target, attributes }]`
- [ ] No schema version field on `TaskGraphSerialized` per spec
- [ ] Re-exported from `src/schema/index.ts`
## References
- docs/architecture/schemas.md — graph attribute schemas, SerializedGraph
- docs/research/typebox-patterns.md — section 6 (generic schema factories)
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

View File

@@ -0,0 +1,39 @@
---
id: schema/input-schemas
name: Define TaskInput, DependencyEdge, and Nullable helper
status: pending
depends_on:
- schema/enums
scope: narrow
risk: low
impact: component
level: implementation
---
## Description
Define the `TaskInput` and `DependencyEdge` input schemas in `src/schema/task.ts`, plus the `Nullable` generic helper. `TaskInput` uses `Type.Optional(Nullable(...))` for categorical fields to support both absent and explicitly-null values (YAML frontmatter distinction).
## Acceptance Criteria
- [ ] `src/schema/task.ts` exports `Nullable` helper: `const Nullable = <T extends TSchema>(T: T) => Type.Union([T, Type.Null()])`
- [ ] `TaskInput` schema defined with all fields per [schemas.md](../../../docs/architecture/schemas.md):
- `id: Type.String()`, `name: Type.String()`, `dependsOn: Type.Array(Type.String())`
- Categorical fields: `Type.Optional(Nullable(TaskXxxEnum))` for status, scope, risk, impact, level, priority
- Metadata fields: `tags`, `assignee`, `due`, `created`, `modified`
- [ ] `DependencyEdge` schema: `from: Type.String()`, `to: Type.String()`, `qualityRetention: Type.Optional(Type.Number({ default: 0.9 }))`
- [ ] Type aliases derived: `type TaskInput = Static<typeof TaskInput>`, `type DependencyEdge = Static<typeof DependencyEdge>`
- [ ] Re-exported from `src/schema/index.ts`
## References
- docs/architecture/schemas.md — TaskInput, DependencyEdge, Nullable definitions
- docs/research/typebox-patterns.md — section 6 (Nullable helper pattern)
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

View File

@@ -0,0 +1,48 @@
---
id: schema/numeric-methods-and-defaults
name: Implement categorical numeric functions and resolveDefaults
status: pending
depends_on:
- schema/enums
- schema/graph-schemas
scope: narrow
risk: low
impact: component
level: implementation
---
## Description
Implement the standalone numeric functions that map categorical enum values to their numeric equivalents, plus `resolveDefaults` which fills in defaults for unassessed fields and computes derived numeric values. These live in `src/analysis/defaults.ts` per the project structure.
## Acceptance Criteria
- [ ] `src/analysis/defaults.ts` exports:
- `scopeCostEstimate(scope: TaskScope): number` — maps to 1.05.0 per table
- `scopeTokenEstimate(scope: TaskScope): number` — maps to 50010000 per table
- `riskSuccessProbability(risk: TaskRisk): number` — maps to 0.500.98 per table
- `riskWeight(risk: TaskRisk): number` — maps to 0.020.50 (equals 1 - successProbability)
- `impactWeight(impact: TaskImpact): number` — maps to 1.03.0 per table
- `resolveDefaults(attrs: Partial<TaskGraphNodeAttributes> & Pick<TaskGraphNodeAttributes, 'name'>): ResolvedTaskAttributes`
- [ ] All numeric mapping tables match [schemas.md](../../../docs/architecture/schemas.md) exactly:
- Scope: single=1.0/500, narrow=2.0/1500, moderate=3.0/3000, broad=4.0/6000, system=5.0/10000
- Risk: trivial=0.98/0.02, low=0.90/0.10, medium=0.80/0.20, high=0.65/0.35, critical=0.50/0.50
- Impact: isolated=1.0, component=1.5, phase=2.0, project=3.0
- [ ] `resolveDefaults` handles null/undefined categorical fields by falling back to: risk→medium, scope→narrow, impact→isolated
- [ ] `resolveDefaults` populates derived fields: costEstimate, tokenEstimate, successProbability, riskWeight, impactWeight
- [ ] Label-only fields (level, priority, status) remain nullable after resolution — no default value assigned
- [ ] `riskWeight(risk)` equals `1 - riskSuccessProbability(risk)` — guaranteed by implementation
- [ ] Unit tests covering every enum value's numeric mapping and resolveDefaults with mixed null/present inputs
## References
- docs/architecture/schemas.md — numeric method tables, ResolvedTaskAttributes definition
- docs/architecture/graph-model.md — categorical field defaults
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

View File

@@ -0,0 +1,42 @@
---
id: schema/result-types
name: Define analysis result TypeBox schemas (RiskPathResult, DecomposeResult, WorkflowCostResult, etc.)
status: pending
depends_on:
- schema/enums
scope: narrow
risk: trivial
impact: component
level: implementation
---
## Description
Define all analysis function return type schemas in `src/schema/results.ts`. These are the structured outputs of the cost-benefit and analysis functions. Each schema has both a TypeBox constant and a `Static<typeof>` type alias.
## Acceptance Criteria
- [ ] `src/schema/results.ts` exports all result schemas and types:
- `RiskPathResult`: `{ path: string[], totalRisk: number }`
- `DecomposeResult`: `{ shouldDecompose: boolean, reasons: string[] }`
- `WorkflowCostOptions`: `{ includeCompleted?, limit?, propagationMode?, defaultQualityRetention? }`
- `WorkflowCostResult`: `{ tasks: [...], totalEv, averageEv, propagationMode }`
- `EvConfig`: `{ retries?, fallbackCost?, timeLost?, valueRate? }` with defaults (0, 0, 0, 0)
- `EvResult`: `{ ev, pSuccess, expectedRetries }`
- `RiskDistributionResult`: `{ trivial, low, medium, high, critical, unspecified }` — each `string[]`
- [ ] All schemas use `Static<typeof>` for type aliases, no manual interface definitions
- [ ] `WorkflowCostOptions.propagationMode` is `Type.Union([Type.Literal("independent"), Type.Literal("dag-propagate")])`
- [ ] Re-exported from `src/schema/index.ts`
## References
- docs/architecture/api-surface.md — return type definitions
- docs/architecture/schemas.md — result schema definitions
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

View File

@@ -0,0 +1,51 @@
---
id: setup/project-init
name: Initialize project with package.json, tsconfig, and build tooling
status: pending
depends_on: []
scope: moderate
risk: low
impact: project
level: implementation
---
## Description
Set up the TypeScript project from scratch. This is a greenfield project — the repo currently has only `AGENTS.md` and `docs/`. Initialize everything needed for a pure TypeScript ESM library: package.json, tsconfig.json, gitignore, and the src/ directory skeleton.
Per [build-distribution.md](../../../docs/architecture/build-distribution.md):
- Package name: `@alkdev/taskgraph`
- ESM primary, CJS compat
- Targets: Node 18+, Deno, Bun (pure JS, no native addons)
- Build: `tsc` for declarations + bundler for distribution
- Dependencies: `graphology`, `graphology-dag`, `graphology-metrics`, `graphology-components`, `graphology-operators`, `@alkdev/typebox`, `yaml`
## Acceptance Criteria
- [ ] `package.json` exists with name `@alkdev/taskgraph`, ESM primary (`"type": "module"`), CJS compat config
- [ ] All production dependencies listed per [build-distribution.md](../../../docs/architecture/build-distribution.md) dependencies table
- [ ] Dev dependencies include: `typescript`, `vitest` (or agreed test runner), `@types/node`
- [ ] `tsconfig.json` configured for Node 18+ target, ESM module resolution, strict mode, declaration output
- [ ] `.gitignore` covers `node_modules/`, `dist/`, `*.js.map`, `.env`
- [ ] `src/` directory skeleton created per [build-distribution.md](../../../docs/architecture/build-distribution.md) project structure:
- `src/index.ts`
- `src/schema/index.ts`, `src/schema/enums.ts`, `src/schema/task.ts`, `src/schema/graph.ts`, `src/schema/results.ts`
- `src/graph/index.ts`, `src/graph/construction.ts`, `src/graph/queries.ts`, `src/graph/mutation.ts`
- `src/analysis/index.ts`, `src/analysis/critical-path.ts`, `src/analysis/bottleneck.ts`, `src/analysis/risk.ts`, `src/analysis/cost-benefit.ts`, `src/analysis/decompose.ts`, `src/analysis/defaults.ts`
- `src/frontmatter/index.ts`, `src/frontmatter/parse.ts`, `src/frontmatter/serialize.ts`
- `src/error/index.ts`
- [ ] `test/` directory created with placeholder test files per build-distribution spec
- [ ] `npm install` succeeds without errors
- [ ] `npx tsc --noEmit` succeeds (empty source files, but config is valid)
## References
- docs/architecture/build-distribution.md — project structure, dependencies, targets
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

View File

@@ -0,0 +1,42 @@
---
id: setup/test-infrastructure
name: Configure test runner and shared test fixtures
status: pending
depends_on:
- setup/project-init
scope: narrow
risk: low
impact: project
level: implementation
---
## Description
Set up the test infrastructure: configure Vitest (or chosen runner), create shared test fixtures and helpers for graph construction that all downstream test files will use. This avoids every test file building graphs from scratch.
## Acceptance Criteria
- [ ] Test runner configured in `package.json` scripts (`"test"`, `"test:watch"`, `"test:coverage"`)
- [ ] Vitest config (or equivalent) exists with ESM support and TypeScript path resolution
- [ ] Shared test fixture file created (e.g., `test/fixtures/graphs.ts`) with:
- A simple linear chain graph (3-4 tasks, A→B→C→D)
- A diamond dependency graph (A→B, A→C, B→D, C→D)
- A graph with mixed categorical fields (some assessed, some null)
- A graph with cycles for testing cycle detection
- A larger graph (20+ nodes) for performance/bottleneck testing
- [ ] Helper function to create a `TaskGraph` from `TaskInput[]` for one-liner test setup
- [ ] Test runner executes successfully against placeholder test files
- [ ] CI-compatible output format (no watch mode in default script)
## References
- docs/architecture/build-distribution.md — test directory structure
- docs/architecture/graph-model.md — graph construction examples for fixtures
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion