From 131e3e929b50de5749c864fbd04c4e5e3ffda52d Mon Sep 17 00:00:00 2001 From: "glm-5.1" Date: Mon, 27 Apr 2026 08:30:05 +0000 Subject: [PATCH] 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. --- tasks/implementation/analysis/bottlenecks.md | 38 ++++++++++++ .../implementation/analysis/critical-path.md | 40 ++++++++++++ .../analysis/parallel-groups.md | 38 ++++++++++++ tasks/implementation/api/public-exports.md | 57 +++++++++++++++++ .../cost-benefit/dag-propagation.md | 51 +++++++++++++++ .../cost-benefit/ev-calculation.md | 48 ++++++++++++++ .../cost-benefit/risk-analysis.md | 50 +++++++++++++++ .../cost-benefit/workflow-cost.md | 45 ++++++++++++++ tasks/implementation/error/error-hierarchy.md | 43 +++++++++++++ .../frontmatter/file-io-and-serialize.md | 54 ++++++++++++++++ tasks/implementation/frontmatter/parsing.md | 51 +++++++++++++++ tasks/implementation/frontmatter/splitter.md | 45 ++++++++++++++ tasks/implementation/graph/construction.md | 62 +++++++++++++++++++ tasks/implementation/graph/export.md | 36 +++++++++++ tasks/implementation/graph/mutation.md | 38 ++++++++++++ tasks/implementation/graph/queries.md | 53 ++++++++++++++++ .../graph/subgraph-and-validation.md | 54 ++++++++++++++++ tasks/implementation/graph/taskgraph-class.md | 45 ++++++++++++++ .../implementation/review/complete-library.md | 48 ++++++++++++++ tasks/implementation/review/graph-complete.md | 53 ++++++++++++++++ .../review/schemas-and-errors.md | 49 +++++++++++++++ tasks/implementation/schema/enums.md | 44 +++++++++++++ tasks/implementation/schema/graph-schemas.md | 42 +++++++++++++ tasks/implementation/schema/input-schemas.md | 39 ++++++++++++ .../schema/numeric-methods-and-defaults.md | 48 ++++++++++++++ tasks/implementation/schema/result-types.md | 42 +++++++++++++ tasks/implementation/setup/project-init.md | 51 +++++++++++++++ .../setup/test-infrastructure.md | 42 +++++++++++++ 28 files changed, 1306 insertions(+) create mode 100644 tasks/implementation/analysis/bottlenecks.md create mode 100644 tasks/implementation/analysis/critical-path.md create mode 100644 tasks/implementation/analysis/parallel-groups.md create mode 100644 tasks/implementation/api/public-exports.md create mode 100644 tasks/implementation/cost-benefit/dag-propagation.md create mode 100644 tasks/implementation/cost-benefit/ev-calculation.md create mode 100644 tasks/implementation/cost-benefit/risk-analysis.md create mode 100644 tasks/implementation/cost-benefit/workflow-cost.md create mode 100644 tasks/implementation/error/error-hierarchy.md create mode 100644 tasks/implementation/frontmatter/file-io-and-serialize.md create mode 100644 tasks/implementation/frontmatter/parsing.md create mode 100644 tasks/implementation/frontmatter/splitter.md create mode 100644 tasks/implementation/graph/construction.md create mode 100644 tasks/implementation/graph/export.md create mode 100644 tasks/implementation/graph/mutation.md create mode 100644 tasks/implementation/graph/queries.md create mode 100644 tasks/implementation/graph/subgraph-and-validation.md create mode 100644 tasks/implementation/graph/taskgraph-class.md create mode 100644 tasks/implementation/review/complete-library.md create mode 100644 tasks/implementation/review/graph-complete.md create mode 100644 tasks/implementation/review/schemas-and-errors.md create mode 100644 tasks/implementation/schema/enums.md create mode 100644 tasks/implementation/schema/graph-schemas.md create mode 100644 tasks/implementation/schema/input-schemas.md create mode 100644 tasks/implementation/schema/numeric-methods-and-defaults.md create mode 100644 tasks/implementation/schema/result-types.md create mode 100644 tasks/implementation/setup/project-init.md create mode 100644 tasks/implementation/setup/test-infrastructure.md diff --git a/tasks/implementation/analysis/bottlenecks.md b/tasks/implementation/analysis/bottlenecks.md new file mode 100644 index 0000000..3d5dc0f --- /dev/null +++ b/tasks/implementation/analysis/bottlenecks.md @@ -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.0–1.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 \ No newline at end of file diff --git a/tasks/implementation/analysis/critical-path.md b/tasks/implementation/analysis/critical-path.md new file mode 100644 index 0000000..3864c66 --- /dev/null +++ b/tasks/implementation/analysis/critical-path.md @@ -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 \ No newline at end of file diff --git a/tasks/implementation/analysis/parallel-groups.md b/tasks/implementation/analysis/parallel-groups.md new file mode 100644 index 0000000..0bb6034 --- /dev/null +++ b/tasks/implementation/analysis/parallel-groups.md @@ -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 \ No newline at end of file diff --git a/tasks/implementation/api/public-exports.md b/tasks/implementation/api/public-exports.md new file mode 100644 index 0000000..94670d6 --- /dev/null +++ b/tasks/implementation/api/public-exports.md @@ -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 \ No newline at end of file diff --git a/tasks/implementation/cost-benefit/dag-propagation.md b/tasks/implementation/cost-benefit/dag-propagation.md new file mode 100644 index 0000000..3a8c378 --- /dev/null +++ b/tasks/implementation/cost-benefit/dag-propagation.md @@ -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 \ No newline at end of file diff --git a/tasks/implementation/cost-benefit/ev-calculation.md b/tasks/implementation/cost-benefit/ev-calculation.md new file mode 100644 index 0000000..41e5ed1 --- /dev/null +++ b/tasks/implementation/cost-benefit/ev-calculation.md @@ -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 \ No newline at end of file diff --git a/tasks/implementation/cost-benefit/risk-analysis.md b/tasks/implementation/cost-benefit/risk-analysis.md new file mode 100644 index 0000000..f820c27 --- /dev/null +++ b/tasks/implementation/cost-benefit/risk-analysis.md @@ -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 \ No newline at end of file diff --git a/tasks/implementation/cost-benefit/workflow-cost.md b/tasks/implementation/cost-benefit/workflow-cost.md new file mode 100644 index 0000000..8228760 --- /dev/null +++ b/tasks/implementation/cost-benefit/workflow-cost.md @@ -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 \ No newline at end of file diff --git a/tasks/implementation/error/error-hierarchy.md b/tasks/implementation/error/error-hierarchy.md new file mode 100644 index 0000000..4fb3fb2 --- /dev/null +++ b/tasks/implementation/error/error-hierarchy.md @@ -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 \ No newline at end of file diff --git a/tasks/implementation/frontmatter/file-io-and-serialize.md b/tasks/implementation/frontmatter/file-io-and-serialize.md new file mode 100644 index 0000000..4a94370 --- /dev/null +++ b/tasks/implementation/frontmatter/file-io-and-serialize.md @@ -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`: + - 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`: + - 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 \ No newline at end of file diff --git a/tasks/implementation/frontmatter/parsing.md b/tasks/implementation/frontmatter/parsing.md new file mode 100644 index 0000000..011725f --- /dev/null +++ b/tasks/implementation/frontmatter/parsing.md @@ -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 \ No newline at end of file diff --git a/tasks/implementation/frontmatter/splitter.md b/tasks/implementation/frontmatter/splitter.md new file mode 100644 index 0000000..6232cd5 --- /dev/null +++ b/tasks/implementation/frontmatter/splitter.md @@ -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 \ No newline at end of file diff --git a/tasks/implementation/graph/construction.md b/tasks/implementation/graph/construction.md new file mode 100644 index 0000000..78aac27 --- /dev/null +++ b/tasks/implementation/graph/construction.md @@ -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 \ No newline at end of file diff --git a/tasks/implementation/graph/export.md b/tasks/implementation/graph/export.md new file mode 100644 index 0000000..473153a --- /dev/null +++ b/tasks/implementation/graph/export.md @@ -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 \ No newline at end of file diff --git a/tasks/implementation/graph/mutation.md b/tasks/implementation/graph/mutation.md new file mode 100644 index 0000000..03be412 --- /dev/null +++ b/tasks/implementation/graph/mutation.md @@ -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): void` — Throws `TaskNotFoundError` if ID doesn't exist. Uses `mergeNodeAttributes` for shallow merge of provided attributes. +- [ ] `updateEdgeAttributes(prerequisite: string, dependent: string, attrs: Partial): 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 \ No newline at end of file diff --git a/tasks/implementation/graph/queries.md b/tasks/implementation/graph/queries.md new file mode 100644 index 0000000..a2019e5 --- /dev/null +++ b/tasks/implementation/graph/queries.md @@ -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 \ No newline at end of file diff --git a/tasks/implementation/graph/subgraph-and-validation.md b/tasks/implementation/graph/subgraph-and-validation.md new file mode 100644 index 0000000..73fe24f --- /dev/null +++ b/tasks/implementation/graph/subgraph-and-validation.md @@ -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 \ No newline at end of file diff --git a/tasks/implementation/graph/taskgraph-class.md b/tasks/implementation/graph/taskgraph-class.md new file mode 100644 index 0000000..5af90d2 --- /dev/null +++ b/tasks/implementation/graph/taskgraph-class.md @@ -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 \ No newline at end of file diff --git a/tasks/implementation/review/complete-library.md b/tasks/implementation/review/complete-library.md new file mode 100644 index 0000000..4bff81e --- /dev/null +++ b/tasks/implementation/review/complete-library.md @@ -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 \ No newline at end of file diff --git a/tasks/implementation/review/graph-complete.md b/tasks/implementation/review/graph-complete.md new file mode 100644 index 0000000..58638f2 --- /dev/null +++ b/tasks/implementation/review/graph-complete.md @@ -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 \ No newline at end of file diff --git a/tasks/implementation/review/schemas-and-errors.md b/tasks/implementation/review/schemas-and-errors.md new file mode 100644 index 0000000..72b2332 --- /dev/null +++ b/tasks/implementation/review/schemas-and-errors.md @@ -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` 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 \ No newline at end of file diff --git a/tasks/implementation/schema/enums.md b/tasks/implementation/schema/enums.md new file mode 100644 index 0000000..44e3511 --- /dev/null +++ b/tasks/implementation/schema/enums.md @@ -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` 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`: `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 \ No newline at end of file diff --git a/tasks/implementation/schema/graph-schemas.md b/tasks/implementation/schema/graph-schemas.md new file mode 100644 index 0000000..ff2f866 --- /dev/null +++ b/tasks/implementation/schema/graph-schemas.md @@ -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 `` + - `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 \ No newline at end of file diff --git a/tasks/implementation/schema/input-schemas.md b/tasks/implementation/schema/input-schemas.md new file mode 100644 index 0000000..2d6eb2a --- /dev/null +++ b/tasks/implementation/schema/input-schemas.md @@ -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: 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`, `type DependencyEdge = Static` +- [ ] 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 \ No newline at end of file diff --git a/tasks/implementation/schema/numeric-methods-and-defaults.md b/tasks/implementation/schema/numeric-methods-and-defaults.md new file mode 100644 index 0000000..423c6d6 --- /dev/null +++ b/tasks/implementation/schema/numeric-methods-and-defaults.md @@ -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.0–5.0 per table + - `scopeTokenEstimate(scope: TaskScope): number` — maps to 500–10000 per table + - `riskSuccessProbability(risk: TaskRisk): number` — maps to 0.50–0.98 per table + - `riskWeight(risk: TaskRisk): number` — maps to 0.02–0.50 (equals 1 - successProbability) + - `impactWeight(impact: TaskImpact): number` — maps to 1.0–3.0 per table + - `resolveDefaults(attrs: Partial & Pick): 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 \ No newline at end of file diff --git a/tasks/implementation/schema/result-types.md b/tasks/implementation/schema/result-types.md new file mode 100644 index 0000000..52a469e --- /dev/null +++ b/tasks/implementation/schema/result-types.md @@ -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` 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` 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 \ No newline at end of file diff --git a/tasks/implementation/setup/project-init.md b/tasks/implementation/setup/project-init.md new file mode 100644 index 0000000..4b13258 --- /dev/null +++ b/tasks/implementation/setup/project-init.md @@ -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 \ No newline at end of file diff --git a/tasks/implementation/setup/test-infrastructure.md b/tasks/implementation/setup/test-infrastructure.md new file mode 100644 index 0000000..d846587 --- /dev/null +++ b/tasks/implementation/setup/test-infrastructure.md @@ -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 \ No newline at end of file