Reference implementation for the Phoenix Architecture. Work in progress. aicoding.leaflet.pub/
ai coding crazy

Taskflow example + scaffold naming collision fix

- New example: examples/taskflow/ — task management + analytics
2 spec files → 29 canonical nodes → 7 IUs → working TypeScript
Generated via Claude Sonnet with typecheck-and-retry
Clean tsc --noEmit, drift detection, provenance tracing

- Fixed scaffold generator: renamed internal vars metrics→_svcMetrics,
modules→_svcModules to avoid collisions with generated module names

+3029 -20
+1454
examples/taskflow/package-lock.json
··· 1 + { 2 + "name": "taskflow", 3 + "version": "0.1.0", 4 + "lockfileVersion": 3, 5 + "requires": true, 6 + "packages": { 7 + "": { 8 + "name": "taskflow", 9 + "version": "0.1.0", 10 + "devDependencies": { 11 + "@types/node": "^22.0.0", 12 + "typescript": "^5.4.0", 13 + "vitest": "^2.0.0" 14 + } 15 + }, 16 + "node_modules/@esbuild/aix-ppc64": { 17 + "version": "0.21.5", 18 + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", 19 + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", 20 + "cpu": [ 21 + "ppc64" 22 + ], 23 + "dev": true, 24 + "license": "MIT", 25 + "optional": true, 26 + "os": [ 27 + "aix" 28 + ], 29 + "engines": { 30 + "node": ">=12" 31 + } 32 + }, 33 + "node_modules/@esbuild/android-arm": { 34 + "version": "0.21.5", 35 + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", 36 + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", 37 + "cpu": [ 38 + "arm" 39 + ], 40 + "dev": true, 41 + "license": "MIT", 42 + "optional": true, 43 + "os": [ 44 + "android" 45 + ], 46 + "engines": { 47 + "node": ">=12" 48 + } 49 + }, 50 + "node_modules/@esbuild/android-arm64": { 51 + "version": "0.21.5", 52 + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", 53 + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", 54 + "cpu": [ 55 + "arm64" 56 + ], 57 + "dev": true, 58 + "license": "MIT", 59 + "optional": true, 60 + "os": [ 61 + "android" 62 + ], 63 + "engines": { 64 + "node": ">=12" 65 + } 66 + }, 67 + "node_modules/@esbuild/android-x64": { 68 + "version": "0.21.5", 69 + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", 70 + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", 71 + "cpu": [ 72 + "x64" 73 + ], 74 + "dev": true, 75 + "license": "MIT", 76 + "optional": true, 77 + "os": [ 78 + "android" 79 + ], 80 + "engines": { 81 + "node": ">=12" 82 + } 83 + }, 84 + "node_modules/@esbuild/darwin-arm64": { 85 + "version": "0.21.5", 86 + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", 87 + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", 88 + "cpu": [ 89 + "arm64" 90 + ], 91 + "dev": true, 92 + "license": "MIT", 93 + "optional": true, 94 + "os": [ 95 + "darwin" 96 + ], 97 + "engines": { 98 + "node": ">=12" 99 + } 100 + }, 101 + "node_modules/@esbuild/darwin-x64": { 102 + "version": "0.21.5", 103 + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", 104 + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", 105 + "cpu": [ 106 + "x64" 107 + ], 108 + "dev": true, 109 + "license": "MIT", 110 + "optional": true, 111 + "os": [ 112 + "darwin" 113 + ], 114 + "engines": { 115 + "node": ">=12" 116 + } 117 + }, 118 + "node_modules/@esbuild/freebsd-arm64": { 119 + "version": "0.21.5", 120 + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", 121 + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", 122 + "cpu": [ 123 + "arm64" 124 + ], 125 + "dev": true, 126 + "license": "MIT", 127 + "optional": true, 128 + "os": [ 129 + "freebsd" 130 + ], 131 + "engines": { 132 + "node": ">=12" 133 + } 134 + }, 135 + "node_modules/@esbuild/freebsd-x64": { 136 + "version": "0.21.5", 137 + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", 138 + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", 139 + "cpu": [ 140 + "x64" 141 + ], 142 + "dev": true, 143 + "license": "MIT", 144 + "optional": true, 145 + "os": [ 146 + "freebsd" 147 + ], 148 + "engines": { 149 + "node": ">=12" 150 + } 151 + }, 152 + "node_modules/@esbuild/linux-arm": { 153 + "version": "0.21.5", 154 + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", 155 + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", 156 + "cpu": [ 157 + "arm" 158 + ], 159 + "dev": true, 160 + "license": "MIT", 161 + "optional": true, 162 + "os": [ 163 + "linux" 164 + ], 165 + "engines": { 166 + "node": ">=12" 167 + } 168 + }, 169 + "node_modules/@esbuild/linux-arm64": { 170 + "version": "0.21.5", 171 + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", 172 + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", 173 + "cpu": [ 174 + "arm64" 175 + ], 176 + "dev": true, 177 + "license": "MIT", 178 + "optional": true, 179 + "os": [ 180 + "linux" 181 + ], 182 + "engines": { 183 + "node": ">=12" 184 + } 185 + }, 186 + "node_modules/@esbuild/linux-ia32": { 187 + "version": "0.21.5", 188 + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", 189 + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", 190 + "cpu": [ 191 + "ia32" 192 + ], 193 + "dev": true, 194 + "license": "MIT", 195 + "optional": true, 196 + "os": [ 197 + "linux" 198 + ], 199 + "engines": { 200 + "node": ">=12" 201 + } 202 + }, 203 + "node_modules/@esbuild/linux-loong64": { 204 + "version": "0.21.5", 205 + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", 206 + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", 207 + "cpu": [ 208 + "loong64" 209 + ], 210 + "dev": true, 211 + "license": "MIT", 212 + "optional": true, 213 + "os": [ 214 + "linux" 215 + ], 216 + "engines": { 217 + "node": ">=12" 218 + } 219 + }, 220 + "node_modules/@esbuild/linux-mips64el": { 221 + "version": "0.21.5", 222 + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", 223 + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", 224 + "cpu": [ 225 + "mips64el" 226 + ], 227 + "dev": true, 228 + "license": "MIT", 229 + "optional": true, 230 + "os": [ 231 + "linux" 232 + ], 233 + "engines": { 234 + "node": ">=12" 235 + } 236 + }, 237 + "node_modules/@esbuild/linux-ppc64": { 238 + "version": "0.21.5", 239 + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", 240 + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", 241 + "cpu": [ 242 + "ppc64" 243 + ], 244 + "dev": true, 245 + "license": "MIT", 246 + "optional": true, 247 + "os": [ 248 + "linux" 249 + ], 250 + "engines": { 251 + "node": ">=12" 252 + } 253 + }, 254 + "node_modules/@esbuild/linux-riscv64": { 255 + "version": "0.21.5", 256 + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", 257 + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", 258 + "cpu": [ 259 + "riscv64" 260 + ], 261 + "dev": true, 262 + "license": "MIT", 263 + "optional": true, 264 + "os": [ 265 + "linux" 266 + ], 267 + "engines": { 268 + "node": ">=12" 269 + } 270 + }, 271 + "node_modules/@esbuild/linux-s390x": { 272 + "version": "0.21.5", 273 + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", 274 + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", 275 + "cpu": [ 276 + "s390x" 277 + ], 278 + "dev": true, 279 + "license": "MIT", 280 + "optional": true, 281 + "os": [ 282 + "linux" 283 + ], 284 + "engines": { 285 + "node": ">=12" 286 + } 287 + }, 288 + "node_modules/@esbuild/linux-x64": { 289 + "version": "0.21.5", 290 + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", 291 + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", 292 + "cpu": [ 293 + "x64" 294 + ], 295 + "dev": true, 296 + "license": "MIT", 297 + "optional": true, 298 + "os": [ 299 + "linux" 300 + ], 301 + "engines": { 302 + "node": ">=12" 303 + } 304 + }, 305 + "node_modules/@esbuild/netbsd-x64": { 306 + "version": "0.21.5", 307 + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", 308 + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", 309 + "cpu": [ 310 + "x64" 311 + ], 312 + "dev": true, 313 + "license": "MIT", 314 + "optional": true, 315 + "os": [ 316 + "netbsd" 317 + ], 318 + "engines": { 319 + "node": ">=12" 320 + } 321 + }, 322 + "node_modules/@esbuild/openbsd-x64": { 323 + "version": "0.21.5", 324 + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", 325 + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", 326 + "cpu": [ 327 + "x64" 328 + ], 329 + "dev": true, 330 + "license": "MIT", 331 + "optional": true, 332 + "os": [ 333 + "openbsd" 334 + ], 335 + "engines": { 336 + "node": ">=12" 337 + } 338 + }, 339 + "node_modules/@esbuild/sunos-x64": { 340 + "version": "0.21.5", 341 + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", 342 + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", 343 + "cpu": [ 344 + "x64" 345 + ], 346 + "dev": true, 347 + "license": "MIT", 348 + "optional": true, 349 + "os": [ 350 + "sunos" 351 + ], 352 + "engines": { 353 + "node": ">=12" 354 + } 355 + }, 356 + "node_modules/@esbuild/win32-arm64": { 357 + "version": "0.21.5", 358 + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", 359 + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", 360 + "cpu": [ 361 + "arm64" 362 + ], 363 + "dev": true, 364 + "license": "MIT", 365 + "optional": true, 366 + "os": [ 367 + "win32" 368 + ], 369 + "engines": { 370 + "node": ">=12" 371 + } 372 + }, 373 + "node_modules/@esbuild/win32-ia32": { 374 + "version": "0.21.5", 375 + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", 376 + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", 377 + "cpu": [ 378 + "ia32" 379 + ], 380 + "dev": true, 381 + "license": "MIT", 382 + "optional": true, 383 + "os": [ 384 + "win32" 385 + ], 386 + "engines": { 387 + "node": ">=12" 388 + } 389 + }, 390 + "node_modules/@esbuild/win32-x64": { 391 + "version": "0.21.5", 392 + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", 393 + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", 394 + "cpu": [ 395 + "x64" 396 + ], 397 + "dev": true, 398 + "license": "MIT", 399 + "optional": true, 400 + "os": [ 401 + "win32" 402 + ], 403 + "engines": { 404 + "node": ">=12" 405 + } 406 + }, 407 + "node_modules/@jridgewell/sourcemap-codec": { 408 + "version": "1.5.5", 409 + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", 410 + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", 411 + "dev": true, 412 + "license": "MIT" 413 + }, 414 + "node_modules/@rollup/rollup-android-arm-eabi": { 415 + "version": "4.57.1", 416 + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", 417 + "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", 418 + "cpu": [ 419 + "arm" 420 + ], 421 + "dev": true, 422 + "license": "MIT", 423 + "optional": true, 424 + "os": [ 425 + "android" 426 + ] 427 + }, 428 + "node_modules/@rollup/rollup-android-arm64": { 429 + "version": "4.57.1", 430 + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", 431 + "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", 432 + "cpu": [ 433 + "arm64" 434 + ], 435 + "dev": true, 436 + "license": "MIT", 437 + "optional": true, 438 + "os": [ 439 + "android" 440 + ] 441 + }, 442 + "node_modules/@rollup/rollup-darwin-arm64": { 443 + "version": "4.57.1", 444 + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", 445 + "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", 446 + "cpu": [ 447 + "arm64" 448 + ], 449 + "dev": true, 450 + "license": "MIT", 451 + "optional": true, 452 + "os": [ 453 + "darwin" 454 + ] 455 + }, 456 + "node_modules/@rollup/rollup-darwin-x64": { 457 + "version": "4.57.1", 458 + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", 459 + "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", 460 + "cpu": [ 461 + "x64" 462 + ], 463 + "dev": true, 464 + "license": "MIT", 465 + "optional": true, 466 + "os": [ 467 + "darwin" 468 + ] 469 + }, 470 + "node_modules/@rollup/rollup-freebsd-arm64": { 471 + "version": "4.57.1", 472 + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", 473 + "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", 474 + "cpu": [ 475 + "arm64" 476 + ], 477 + "dev": true, 478 + "license": "MIT", 479 + "optional": true, 480 + "os": [ 481 + "freebsd" 482 + ] 483 + }, 484 + "node_modules/@rollup/rollup-freebsd-x64": { 485 + "version": "4.57.1", 486 + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", 487 + "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", 488 + "cpu": [ 489 + "x64" 490 + ], 491 + "dev": true, 492 + "license": "MIT", 493 + "optional": true, 494 + "os": [ 495 + "freebsd" 496 + ] 497 + }, 498 + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { 499 + "version": "4.57.1", 500 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", 501 + "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", 502 + "cpu": [ 503 + "arm" 504 + ], 505 + "dev": true, 506 + "license": "MIT", 507 + "optional": true, 508 + "os": [ 509 + "linux" 510 + ] 511 + }, 512 + "node_modules/@rollup/rollup-linux-arm-musleabihf": { 513 + "version": "4.57.1", 514 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", 515 + "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", 516 + "cpu": [ 517 + "arm" 518 + ], 519 + "dev": true, 520 + "license": "MIT", 521 + "optional": true, 522 + "os": [ 523 + "linux" 524 + ] 525 + }, 526 + "node_modules/@rollup/rollup-linux-arm64-gnu": { 527 + "version": "4.57.1", 528 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", 529 + "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", 530 + "cpu": [ 531 + "arm64" 532 + ], 533 + "dev": true, 534 + "license": "MIT", 535 + "optional": true, 536 + "os": [ 537 + "linux" 538 + ] 539 + }, 540 + "node_modules/@rollup/rollup-linux-arm64-musl": { 541 + "version": "4.57.1", 542 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", 543 + "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", 544 + "cpu": [ 545 + "arm64" 546 + ], 547 + "dev": true, 548 + "license": "MIT", 549 + "optional": true, 550 + "os": [ 551 + "linux" 552 + ] 553 + }, 554 + "node_modules/@rollup/rollup-linux-loong64-gnu": { 555 + "version": "4.57.1", 556 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", 557 + "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", 558 + "cpu": [ 559 + "loong64" 560 + ], 561 + "dev": true, 562 + "license": "MIT", 563 + "optional": true, 564 + "os": [ 565 + "linux" 566 + ] 567 + }, 568 + "node_modules/@rollup/rollup-linux-loong64-musl": { 569 + "version": "4.57.1", 570 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", 571 + "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", 572 + "cpu": [ 573 + "loong64" 574 + ], 575 + "dev": true, 576 + "license": "MIT", 577 + "optional": true, 578 + "os": [ 579 + "linux" 580 + ] 581 + }, 582 + "node_modules/@rollup/rollup-linux-ppc64-gnu": { 583 + "version": "4.57.1", 584 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", 585 + "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", 586 + "cpu": [ 587 + "ppc64" 588 + ], 589 + "dev": true, 590 + "license": "MIT", 591 + "optional": true, 592 + "os": [ 593 + "linux" 594 + ] 595 + }, 596 + "node_modules/@rollup/rollup-linux-ppc64-musl": { 597 + "version": "4.57.1", 598 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", 599 + "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", 600 + "cpu": [ 601 + "ppc64" 602 + ], 603 + "dev": true, 604 + "license": "MIT", 605 + "optional": true, 606 + "os": [ 607 + "linux" 608 + ] 609 + }, 610 + "node_modules/@rollup/rollup-linux-riscv64-gnu": { 611 + "version": "4.57.1", 612 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", 613 + "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", 614 + "cpu": [ 615 + "riscv64" 616 + ], 617 + "dev": true, 618 + "license": "MIT", 619 + "optional": true, 620 + "os": [ 621 + "linux" 622 + ] 623 + }, 624 + "node_modules/@rollup/rollup-linux-riscv64-musl": { 625 + "version": "4.57.1", 626 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", 627 + "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", 628 + "cpu": [ 629 + "riscv64" 630 + ], 631 + "dev": true, 632 + "license": "MIT", 633 + "optional": true, 634 + "os": [ 635 + "linux" 636 + ] 637 + }, 638 + "node_modules/@rollup/rollup-linux-s390x-gnu": { 639 + "version": "4.57.1", 640 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", 641 + "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", 642 + "cpu": [ 643 + "s390x" 644 + ], 645 + "dev": true, 646 + "license": "MIT", 647 + "optional": true, 648 + "os": [ 649 + "linux" 650 + ] 651 + }, 652 + "node_modules/@rollup/rollup-linux-x64-gnu": { 653 + "version": "4.57.1", 654 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", 655 + "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", 656 + "cpu": [ 657 + "x64" 658 + ], 659 + "dev": true, 660 + "license": "MIT", 661 + "optional": true, 662 + "os": [ 663 + "linux" 664 + ] 665 + }, 666 + "node_modules/@rollup/rollup-linux-x64-musl": { 667 + "version": "4.57.1", 668 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", 669 + "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", 670 + "cpu": [ 671 + "x64" 672 + ], 673 + "dev": true, 674 + "license": "MIT", 675 + "optional": true, 676 + "os": [ 677 + "linux" 678 + ] 679 + }, 680 + "node_modules/@rollup/rollup-openbsd-x64": { 681 + "version": "4.57.1", 682 + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", 683 + "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", 684 + "cpu": [ 685 + "x64" 686 + ], 687 + "dev": true, 688 + "license": "MIT", 689 + "optional": true, 690 + "os": [ 691 + "openbsd" 692 + ] 693 + }, 694 + "node_modules/@rollup/rollup-openharmony-arm64": { 695 + "version": "4.57.1", 696 + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", 697 + "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", 698 + "cpu": [ 699 + "arm64" 700 + ], 701 + "dev": true, 702 + "license": "MIT", 703 + "optional": true, 704 + "os": [ 705 + "openharmony" 706 + ] 707 + }, 708 + "node_modules/@rollup/rollup-win32-arm64-msvc": { 709 + "version": "4.57.1", 710 + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", 711 + "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", 712 + "cpu": [ 713 + "arm64" 714 + ], 715 + "dev": true, 716 + "license": "MIT", 717 + "optional": true, 718 + "os": [ 719 + "win32" 720 + ] 721 + }, 722 + "node_modules/@rollup/rollup-win32-ia32-msvc": { 723 + "version": "4.57.1", 724 + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", 725 + "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", 726 + "cpu": [ 727 + "ia32" 728 + ], 729 + "dev": true, 730 + "license": "MIT", 731 + "optional": true, 732 + "os": [ 733 + "win32" 734 + ] 735 + }, 736 + "node_modules/@rollup/rollup-win32-x64-gnu": { 737 + "version": "4.57.1", 738 + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", 739 + "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", 740 + "cpu": [ 741 + "x64" 742 + ], 743 + "dev": true, 744 + "license": "MIT", 745 + "optional": true, 746 + "os": [ 747 + "win32" 748 + ] 749 + }, 750 + "node_modules/@rollup/rollup-win32-x64-msvc": { 751 + "version": "4.57.1", 752 + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", 753 + "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", 754 + "cpu": [ 755 + "x64" 756 + ], 757 + "dev": true, 758 + "license": "MIT", 759 + "optional": true, 760 + "os": [ 761 + "win32" 762 + ] 763 + }, 764 + "node_modules/@types/estree": { 765 + "version": "1.0.8", 766 + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", 767 + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", 768 + "dev": true, 769 + "license": "MIT" 770 + }, 771 + "node_modules/@types/node": { 772 + "version": "22.19.11", 773 + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.11.tgz", 774 + "integrity": "sha512-BH7YwL6rA93ReqeQS1c4bsPpcfOmJasG+Fkr6Y59q83f9M1WcBRHR2vM+P9eOisYRcN3ujQoiZY8uk5W+1WL8w==", 775 + "dev": true, 776 + "license": "MIT", 777 + "dependencies": { 778 + "undici-types": "~6.21.0" 779 + } 780 + }, 781 + "node_modules/@vitest/expect": { 782 + "version": "2.1.9", 783 + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz", 784 + "integrity": "sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==", 785 + "dev": true, 786 + "license": "MIT", 787 + "dependencies": { 788 + "@vitest/spy": "2.1.9", 789 + "@vitest/utils": "2.1.9", 790 + "chai": "^5.1.2", 791 + "tinyrainbow": "^1.2.0" 792 + }, 793 + "funding": { 794 + "url": "https://opencollective.com/vitest" 795 + } 796 + }, 797 + "node_modules/@vitest/mocker": { 798 + "version": "2.1.9", 799 + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.9.tgz", 800 + "integrity": "sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==", 801 + "dev": true, 802 + "license": "MIT", 803 + "dependencies": { 804 + "@vitest/spy": "2.1.9", 805 + "estree-walker": "^3.0.3", 806 + "magic-string": "^0.30.12" 807 + }, 808 + "funding": { 809 + "url": "https://opencollective.com/vitest" 810 + }, 811 + "peerDependencies": { 812 + "msw": "^2.4.9", 813 + "vite": "^5.0.0" 814 + }, 815 + "peerDependenciesMeta": { 816 + "msw": { 817 + "optional": true 818 + }, 819 + "vite": { 820 + "optional": true 821 + } 822 + } 823 + }, 824 + "node_modules/@vitest/pretty-format": { 825 + "version": "2.1.9", 826 + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.9.tgz", 827 + "integrity": "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==", 828 + "dev": true, 829 + "license": "MIT", 830 + "dependencies": { 831 + "tinyrainbow": "^1.2.0" 832 + }, 833 + "funding": { 834 + "url": "https://opencollective.com/vitest" 835 + } 836 + }, 837 + "node_modules/@vitest/runner": { 838 + "version": "2.1.9", 839 + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.9.tgz", 840 + "integrity": "sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==", 841 + "dev": true, 842 + "license": "MIT", 843 + "dependencies": { 844 + "@vitest/utils": "2.1.9", 845 + "pathe": "^1.1.2" 846 + }, 847 + "funding": { 848 + "url": "https://opencollective.com/vitest" 849 + } 850 + }, 851 + "node_modules/@vitest/snapshot": { 852 + "version": "2.1.9", 853 + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.9.tgz", 854 + "integrity": "sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==", 855 + "dev": true, 856 + "license": "MIT", 857 + "dependencies": { 858 + "@vitest/pretty-format": "2.1.9", 859 + "magic-string": "^0.30.12", 860 + "pathe": "^1.1.2" 861 + }, 862 + "funding": { 863 + "url": "https://opencollective.com/vitest" 864 + } 865 + }, 866 + "node_modules/@vitest/spy": { 867 + "version": "2.1.9", 868 + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.9.tgz", 869 + "integrity": "sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==", 870 + "dev": true, 871 + "license": "MIT", 872 + "dependencies": { 873 + "tinyspy": "^3.0.2" 874 + }, 875 + "funding": { 876 + "url": "https://opencollective.com/vitest" 877 + } 878 + }, 879 + "node_modules/@vitest/utils": { 880 + "version": "2.1.9", 881 + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.9.tgz", 882 + "integrity": "sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==", 883 + "dev": true, 884 + "license": "MIT", 885 + "dependencies": { 886 + "@vitest/pretty-format": "2.1.9", 887 + "loupe": "^3.1.2", 888 + "tinyrainbow": "^1.2.0" 889 + }, 890 + "funding": { 891 + "url": "https://opencollective.com/vitest" 892 + } 893 + }, 894 + "node_modules/assertion-error": { 895 + "version": "2.0.1", 896 + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", 897 + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", 898 + "dev": true, 899 + "license": "MIT", 900 + "engines": { 901 + "node": ">=12" 902 + } 903 + }, 904 + "node_modules/cac": { 905 + "version": "6.7.14", 906 + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", 907 + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", 908 + "dev": true, 909 + "license": "MIT", 910 + "engines": { 911 + "node": ">=8" 912 + } 913 + }, 914 + "node_modules/chai": { 915 + "version": "5.3.3", 916 + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", 917 + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", 918 + "dev": true, 919 + "license": "MIT", 920 + "dependencies": { 921 + "assertion-error": "^2.0.1", 922 + "check-error": "^2.1.1", 923 + "deep-eql": "^5.0.1", 924 + "loupe": "^3.1.0", 925 + "pathval": "^2.0.0" 926 + }, 927 + "engines": { 928 + "node": ">=18" 929 + } 930 + }, 931 + "node_modules/check-error": { 932 + "version": "2.1.3", 933 + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", 934 + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", 935 + "dev": true, 936 + "license": "MIT", 937 + "engines": { 938 + "node": ">= 16" 939 + } 940 + }, 941 + "node_modules/debug": { 942 + "version": "4.4.3", 943 + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", 944 + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", 945 + "dev": true, 946 + "license": "MIT", 947 + "dependencies": { 948 + "ms": "^2.1.3" 949 + }, 950 + "engines": { 951 + "node": ">=6.0" 952 + }, 953 + "peerDependenciesMeta": { 954 + "supports-color": { 955 + "optional": true 956 + } 957 + } 958 + }, 959 + "node_modules/deep-eql": { 960 + "version": "5.0.2", 961 + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", 962 + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", 963 + "dev": true, 964 + "license": "MIT", 965 + "engines": { 966 + "node": ">=6" 967 + } 968 + }, 969 + "node_modules/es-module-lexer": { 970 + "version": "1.7.0", 971 + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", 972 + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", 973 + "dev": true, 974 + "license": "MIT" 975 + }, 976 + "node_modules/esbuild": { 977 + "version": "0.21.5", 978 + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", 979 + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", 980 + "dev": true, 981 + "hasInstallScript": true, 982 + "license": "MIT", 983 + "bin": { 984 + "esbuild": "bin/esbuild" 985 + }, 986 + "engines": { 987 + "node": ">=12" 988 + }, 989 + "optionalDependencies": { 990 + "@esbuild/aix-ppc64": "0.21.5", 991 + "@esbuild/android-arm": "0.21.5", 992 + "@esbuild/android-arm64": "0.21.5", 993 + "@esbuild/android-x64": "0.21.5", 994 + "@esbuild/darwin-arm64": "0.21.5", 995 + "@esbuild/darwin-x64": "0.21.5", 996 + "@esbuild/freebsd-arm64": "0.21.5", 997 + "@esbuild/freebsd-x64": "0.21.5", 998 + "@esbuild/linux-arm": "0.21.5", 999 + "@esbuild/linux-arm64": "0.21.5", 1000 + "@esbuild/linux-ia32": "0.21.5", 1001 + "@esbuild/linux-loong64": "0.21.5", 1002 + "@esbuild/linux-mips64el": "0.21.5", 1003 + "@esbuild/linux-ppc64": "0.21.5", 1004 + "@esbuild/linux-riscv64": "0.21.5", 1005 + "@esbuild/linux-s390x": "0.21.5", 1006 + "@esbuild/linux-x64": "0.21.5", 1007 + "@esbuild/netbsd-x64": "0.21.5", 1008 + "@esbuild/openbsd-x64": "0.21.5", 1009 + "@esbuild/sunos-x64": "0.21.5", 1010 + "@esbuild/win32-arm64": "0.21.5", 1011 + "@esbuild/win32-ia32": "0.21.5", 1012 + "@esbuild/win32-x64": "0.21.5" 1013 + } 1014 + }, 1015 + "node_modules/estree-walker": { 1016 + "version": "3.0.3", 1017 + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", 1018 + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", 1019 + "dev": true, 1020 + "license": "MIT", 1021 + "dependencies": { 1022 + "@types/estree": "^1.0.0" 1023 + } 1024 + }, 1025 + "node_modules/expect-type": { 1026 + "version": "1.3.0", 1027 + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", 1028 + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", 1029 + "dev": true, 1030 + "license": "Apache-2.0", 1031 + "engines": { 1032 + "node": ">=12.0.0" 1033 + } 1034 + }, 1035 + "node_modules/fsevents": { 1036 + "version": "2.3.3", 1037 + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 1038 + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 1039 + "dev": true, 1040 + "hasInstallScript": true, 1041 + "license": "MIT", 1042 + "optional": true, 1043 + "os": [ 1044 + "darwin" 1045 + ], 1046 + "engines": { 1047 + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 1048 + } 1049 + }, 1050 + "node_modules/loupe": { 1051 + "version": "3.2.1", 1052 + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", 1053 + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", 1054 + "dev": true, 1055 + "license": "MIT" 1056 + }, 1057 + "node_modules/magic-string": { 1058 + "version": "0.30.21", 1059 + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", 1060 + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", 1061 + "dev": true, 1062 + "license": "MIT", 1063 + "dependencies": { 1064 + "@jridgewell/sourcemap-codec": "^1.5.5" 1065 + } 1066 + }, 1067 + "node_modules/ms": { 1068 + "version": "2.1.3", 1069 + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 1070 + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 1071 + "dev": true, 1072 + "license": "MIT" 1073 + }, 1074 + "node_modules/nanoid": { 1075 + "version": "3.3.11", 1076 + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", 1077 + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", 1078 + "dev": true, 1079 + "funding": [ 1080 + { 1081 + "type": "github", 1082 + "url": "https://github.com/sponsors/ai" 1083 + } 1084 + ], 1085 + "license": "MIT", 1086 + "bin": { 1087 + "nanoid": "bin/nanoid.cjs" 1088 + }, 1089 + "engines": { 1090 + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 1091 + } 1092 + }, 1093 + "node_modules/pathe": { 1094 + "version": "1.1.2", 1095 + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", 1096 + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", 1097 + "dev": true, 1098 + "license": "MIT" 1099 + }, 1100 + "node_modules/pathval": { 1101 + "version": "2.0.1", 1102 + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", 1103 + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", 1104 + "dev": true, 1105 + "license": "MIT", 1106 + "engines": { 1107 + "node": ">= 14.16" 1108 + } 1109 + }, 1110 + "node_modules/picocolors": { 1111 + "version": "1.1.1", 1112 + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", 1113 + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", 1114 + "dev": true, 1115 + "license": "ISC" 1116 + }, 1117 + "node_modules/postcss": { 1118 + "version": "8.5.6", 1119 + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", 1120 + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", 1121 + "dev": true, 1122 + "funding": [ 1123 + { 1124 + "type": "opencollective", 1125 + "url": "https://opencollective.com/postcss/" 1126 + }, 1127 + { 1128 + "type": "tidelift", 1129 + "url": "https://tidelift.com/funding/github/npm/postcss" 1130 + }, 1131 + { 1132 + "type": "github", 1133 + "url": "https://github.com/sponsors/ai" 1134 + } 1135 + ], 1136 + "license": "MIT", 1137 + "dependencies": { 1138 + "nanoid": "^3.3.11", 1139 + "picocolors": "^1.1.1", 1140 + "source-map-js": "^1.2.1" 1141 + }, 1142 + "engines": { 1143 + "node": "^10 || ^12 || >=14" 1144 + } 1145 + }, 1146 + "node_modules/rollup": { 1147 + "version": "4.57.1", 1148 + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", 1149 + "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", 1150 + "dev": true, 1151 + "license": "MIT", 1152 + "dependencies": { 1153 + "@types/estree": "1.0.8" 1154 + }, 1155 + "bin": { 1156 + "rollup": "dist/bin/rollup" 1157 + }, 1158 + "engines": { 1159 + "node": ">=18.0.0", 1160 + "npm": ">=8.0.0" 1161 + }, 1162 + "optionalDependencies": { 1163 + "@rollup/rollup-android-arm-eabi": "4.57.1", 1164 + "@rollup/rollup-android-arm64": "4.57.1", 1165 + "@rollup/rollup-darwin-arm64": "4.57.1", 1166 + "@rollup/rollup-darwin-x64": "4.57.1", 1167 + "@rollup/rollup-freebsd-arm64": "4.57.1", 1168 + "@rollup/rollup-freebsd-x64": "4.57.1", 1169 + "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", 1170 + "@rollup/rollup-linux-arm-musleabihf": "4.57.1", 1171 + "@rollup/rollup-linux-arm64-gnu": "4.57.1", 1172 + "@rollup/rollup-linux-arm64-musl": "4.57.1", 1173 + "@rollup/rollup-linux-loong64-gnu": "4.57.1", 1174 + "@rollup/rollup-linux-loong64-musl": "4.57.1", 1175 + "@rollup/rollup-linux-ppc64-gnu": "4.57.1", 1176 + "@rollup/rollup-linux-ppc64-musl": "4.57.1", 1177 + "@rollup/rollup-linux-riscv64-gnu": "4.57.1", 1178 + "@rollup/rollup-linux-riscv64-musl": "4.57.1", 1179 + "@rollup/rollup-linux-s390x-gnu": "4.57.1", 1180 + "@rollup/rollup-linux-x64-gnu": "4.57.1", 1181 + "@rollup/rollup-linux-x64-musl": "4.57.1", 1182 + "@rollup/rollup-openbsd-x64": "4.57.1", 1183 + "@rollup/rollup-openharmony-arm64": "4.57.1", 1184 + "@rollup/rollup-win32-arm64-msvc": "4.57.1", 1185 + "@rollup/rollup-win32-ia32-msvc": "4.57.1", 1186 + "@rollup/rollup-win32-x64-gnu": "4.57.1", 1187 + "@rollup/rollup-win32-x64-msvc": "4.57.1", 1188 + "fsevents": "~2.3.2" 1189 + } 1190 + }, 1191 + "node_modules/siginfo": { 1192 + "version": "2.0.0", 1193 + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", 1194 + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", 1195 + "dev": true, 1196 + "license": "ISC" 1197 + }, 1198 + "node_modules/source-map-js": { 1199 + "version": "1.2.1", 1200 + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", 1201 + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", 1202 + "dev": true, 1203 + "license": "BSD-3-Clause", 1204 + "engines": { 1205 + "node": ">=0.10.0" 1206 + } 1207 + }, 1208 + "node_modules/stackback": { 1209 + "version": "0.0.2", 1210 + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", 1211 + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", 1212 + "dev": true, 1213 + "license": "MIT" 1214 + }, 1215 + "node_modules/std-env": { 1216 + "version": "3.10.0", 1217 + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", 1218 + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", 1219 + "dev": true, 1220 + "license": "MIT" 1221 + }, 1222 + "node_modules/tinybench": { 1223 + "version": "2.9.0", 1224 + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", 1225 + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", 1226 + "dev": true, 1227 + "license": "MIT" 1228 + }, 1229 + "node_modules/tinyexec": { 1230 + "version": "0.3.2", 1231 + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", 1232 + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", 1233 + "dev": true, 1234 + "license": "MIT" 1235 + }, 1236 + "node_modules/tinypool": { 1237 + "version": "1.1.1", 1238 + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", 1239 + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", 1240 + "dev": true, 1241 + "license": "MIT", 1242 + "engines": { 1243 + "node": "^18.0.0 || >=20.0.0" 1244 + } 1245 + }, 1246 + "node_modules/tinyrainbow": { 1247 + "version": "1.2.0", 1248 + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", 1249 + "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", 1250 + "dev": true, 1251 + "license": "MIT", 1252 + "engines": { 1253 + "node": ">=14.0.0" 1254 + } 1255 + }, 1256 + "node_modules/tinyspy": { 1257 + "version": "3.0.2", 1258 + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", 1259 + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", 1260 + "dev": true, 1261 + "license": "MIT", 1262 + "engines": { 1263 + "node": ">=14.0.0" 1264 + } 1265 + }, 1266 + "node_modules/typescript": { 1267 + "version": "5.9.3", 1268 + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", 1269 + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", 1270 + "dev": true, 1271 + "license": "Apache-2.0", 1272 + "bin": { 1273 + "tsc": "bin/tsc", 1274 + "tsserver": "bin/tsserver" 1275 + }, 1276 + "engines": { 1277 + "node": ">=14.17" 1278 + } 1279 + }, 1280 + "node_modules/undici-types": { 1281 + "version": "6.21.0", 1282 + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", 1283 + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", 1284 + "dev": true, 1285 + "license": "MIT" 1286 + }, 1287 + "node_modules/vite": { 1288 + "version": "5.4.21", 1289 + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", 1290 + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", 1291 + "dev": true, 1292 + "license": "MIT", 1293 + "dependencies": { 1294 + "esbuild": "^0.21.3", 1295 + "postcss": "^8.4.43", 1296 + "rollup": "^4.20.0" 1297 + }, 1298 + "bin": { 1299 + "vite": "bin/vite.js" 1300 + }, 1301 + "engines": { 1302 + "node": "^18.0.0 || >=20.0.0" 1303 + }, 1304 + "funding": { 1305 + "url": "https://github.com/vitejs/vite?sponsor=1" 1306 + }, 1307 + "optionalDependencies": { 1308 + "fsevents": "~2.3.3" 1309 + }, 1310 + "peerDependencies": { 1311 + "@types/node": "^18.0.0 || >=20.0.0", 1312 + "less": "*", 1313 + "lightningcss": "^1.21.0", 1314 + "sass": "*", 1315 + "sass-embedded": "*", 1316 + "stylus": "*", 1317 + "sugarss": "*", 1318 + "terser": "^5.4.0" 1319 + }, 1320 + "peerDependenciesMeta": { 1321 + "@types/node": { 1322 + "optional": true 1323 + }, 1324 + "less": { 1325 + "optional": true 1326 + }, 1327 + "lightningcss": { 1328 + "optional": true 1329 + }, 1330 + "sass": { 1331 + "optional": true 1332 + }, 1333 + "sass-embedded": { 1334 + "optional": true 1335 + }, 1336 + "stylus": { 1337 + "optional": true 1338 + }, 1339 + "sugarss": { 1340 + "optional": true 1341 + }, 1342 + "terser": { 1343 + "optional": true 1344 + } 1345 + } 1346 + }, 1347 + "node_modules/vite-node": { 1348 + "version": "2.1.9", 1349 + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.9.tgz", 1350 + "integrity": "sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==", 1351 + "dev": true, 1352 + "license": "MIT", 1353 + "dependencies": { 1354 + "cac": "^6.7.14", 1355 + "debug": "^4.3.7", 1356 + "es-module-lexer": "^1.5.4", 1357 + "pathe": "^1.1.2", 1358 + "vite": "^5.0.0" 1359 + }, 1360 + "bin": { 1361 + "vite-node": "vite-node.mjs" 1362 + }, 1363 + "engines": { 1364 + "node": "^18.0.0 || >=20.0.0" 1365 + }, 1366 + "funding": { 1367 + "url": "https://opencollective.com/vitest" 1368 + } 1369 + }, 1370 + "node_modules/vitest": { 1371 + "version": "2.1.9", 1372 + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.9.tgz", 1373 + "integrity": "sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==", 1374 + "dev": true, 1375 + "license": "MIT", 1376 + "dependencies": { 1377 + "@vitest/expect": "2.1.9", 1378 + "@vitest/mocker": "2.1.9", 1379 + "@vitest/pretty-format": "^2.1.9", 1380 + "@vitest/runner": "2.1.9", 1381 + "@vitest/snapshot": "2.1.9", 1382 + "@vitest/spy": "2.1.9", 1383 + "@vitest/utils": "2.1.9", 1384 + "chai": "^5.1.2", 1385 + "debug": "^4.3.7", 1386 + "expect-type": "^1.1.0", 1387 + "magic-string": "^0.30.12", 1388 + "pathe": "^1.1.2", 1389 + "std-env": "^3.8.0", 1390 + "tinybench": "^2.9.0", 1391 + "tinyexec": "^0.3.1", 1392 + "tinypool": "^1.0.1", 1393 + "tinyrainbow": "^1.2.0", 1394 + "vite": "^5.0.0", 1395 + "vite-node": "2.1.9", 1396 + "why-is-node-running": "^2.3.0" 1397 + }, 1398 + "bin": { 1399 + "vitest": "vitest.mjs" 1400 + }, 1401 + "engines": { 1402 + "node": "^18.0.0 || >=20.0.0" 1403 + }, 1404 + "funding": { 1405 + "url": "https://opencollective.com/vitest" 1406 + }, 1407 + "peerDependencies": { 1408 + "@edge-runtime/vm": "*", 1409 + "@types/node": "^18.0.0 || >=20.0.0", 1410 + "@vitest/browser": "2.1.9", 1411 + "@vitest/ui": "2.1.9", 1412 + "happy-dom": "*", 1413 + "jsdom": "*" 1414 + }, 1415 + "peerDependenciesMeta": { 1416 + "@edge-runtime/vm": { 1417 + "optional": true 1418 + }, 1419 + "@types/node": { 1420 + "optional": true 1421 + }, 1422 + "@vitest/browser": { 1423 + "optional": true 1424 + }, 1425 + "@vitest/ui": { 1426 + "optional": true 1427 + }, 1428 + "happy-dom": { 1429 + "optional": true 1430 + }, 1431 + "jsdom": { 1432 + "optional": true 1433 + } 1434 + } 1435 + }, 1436 + "node_modules/why-is-node-running": { 1437 + "version": "2.3.0", 1438 + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", 1439 + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", 1440 + "dev": true, 1441 + "license": "MIT", 1442 + "dependencies": { 1443 + "siginfo": "^2.0.0", 1444 + "stackback": "0.0.2" 1445 + }, 1446 + "bin": { 1447 + "why-is-node-running": "cli.js" 1448 + }, 1449 + "engines": { 1450 + "node": ">=8" 1451 + } 1452 + } 1453 + } 1454 + }
+20
examples/taskflow/package.json
··· 1 + { 2 + "name": "taskflow", 3 + "version": "0.1.0", 4 + "description": "Generated by Phoenix VCS — 2 services", 5 + "type": "module", 6 + "scripts": { 7 + "build": "tsc", 8 + "typecheck": "tsc --noEmit", 9 + "test": "vitest run", 10 + "test:watch": "vitest", 11 + "start:analytics": "tsc && node dist/generated/analytics/server.js", 12 + "start:tasks": "tsc && node dist/generated/tasks/server.js", 13 + "start": "tsc && node dist/generated/analytics/server.js" 14 + }, 15 + "devDependencies": { 16 + "typescript": "^5.4.0", 17 + "vitest": "^2.0.0", 18 + "@types/node": "^22.0.0" 19 + } 20 + }
+22
examples/taskflow/spec/analytics.md
··· 1 + # Analytics Dashboard 2 + 3 + Real-time analytics for task management metrics. 4 + 5 + ## Metrics 6 + 7 + - The system must track total tasks created, completed, and overdue 8 + - The system must calculate average task completion time in hours 9 + - The system must compute throughput as tasks completed per day over a rolling 7-day window 10 + - Metrics must be computable from an array of task records (no database dependency) 11 + 12 + ## Priority Breakdown 13 + 14 + - The system must report task count grouped by priority level 15 + - The system must report task count grouped by current status 16 + - Each breakdown must include percentage of total 17 + 18 + ## Team Performance 19 + 20 + - The system must calculate per-assignee completion rate (done / total assigned) 21 + - The system must identify the top performer (highest completion rate with minimum 3 tasks) 22 + - Unassigned tasks must be excluded from team performance metrics
+33
examples/taskflow/spec/tasks.md
··· 1 + # Task Management Service 2 + 3 + A task management system for teams with priorities, assignments, and deadlines. 4 + 5 + ## Task Lifecycle 6 + 7 + - Users must create tasks with a title, description, and priority (low, medium, high, critical) 8 + - Each task must have a unique ID generated as a UUID v4 9 + - Tasks must support status transitions: open → in_progress → review → done 10 + - Invalid status transitions must be rejected with a clear error message 11 + - Tasks must track created_at and updated_at timestamps automatically 12 + - Completing a task must record the completion timestamp and duration 13 + 14 + ## Assignment 15 + 16 + - Tasks must be assignable to a single user by user ID 17 + - Reassigning a task must log the previous assignee in an audit trail 18 + - Unassigned tasks must be queryable as a filtered list 19 + - Assignment must validate that the user ID is non-empty 20 + 21 + ## Search and Filtering 22 + 23 + - Tasks must be searchable by title substring (case-insensitive) 24 + - Tasks must be filterable by status, priority, and assignee 25 + - Search results must be sorted by priority (critical first) then by created_at 26 + - An empty search query must return all tasks 27 + 28 + ## Deadline Management 29 + 30 + - Tasks must support optional deadline dates 31 + - Overdue tasks (past deadline and not done) must be flagged automatically 32 + - The system must provide a function to list all overdue tasks 33 + - Setting a deadline in the past must produce a warning but still be allowed
+92
examples/taskflow/src/generated/analytics/__tests__/analytics.test.ts
··· 1 + /** 2 + * Analytics — Generated Tests 3 + * 4 + * AUTO-GENERATED by Phoenix VCS 5 + * Tests module structure, server health, and Phoenix traceability. 6 + */ 7 + 8 + import { describe, it, expect, afterAll } from 'vitest'; 9 + import { startServer } from '../server.js'; 10 + 11 + import * as metrics from '../metrics.js'; 12 + import * as priorityBreakdown from '../priority-breakdown.js'; 13 + import * as teamPerformance from '../team-performance.js'; 14 + 15 + describe('Analytics modules', () => { 16 + describe('Metrics', () => { 17 + it('exports Phoenix traceability metadata', () => { 18 + expect(metrics._phoenix).toBeDefined(); 19 + expect(metrics._phoenix.name).toBe('Metrics'); 20 + expect(metrics._phoenix.risk_tier).toBeTruthy(); 21 + }); 22 + 23 + it('has exported functions', () => { 24 + const exports = Object.keys(metrics).filter(k => k !== '_phoenix'); 25 + expect(exports.length).toBeGreaterThan(0); 26 + }); 27 + }); 28 + 29 + describe('Priority Breakdown', () => { 30 + it('exports Phoenix traceability metadata', () => { 31 + expect(priorityBreakdown._phoenix).toBeDefined(); 32 + expect(priorityBreakdown._phoenix.name).toBe('Priority Breakdown'); 33 + expect(priorityBreakdown._phoenix.risk_tier).toBeTruthy(); 34 + }); 35 + 36 + it('has exported functions', () => { 37 + const exports = Object.keys(priorityBreakdown).filter(k => k !== '_phoenix'); 38 + expect(exports.length).toBeGreaterThan(0); 39 + }); 40 + }); 41 + 42 + describe('Team Performance', () => { 43 + it('exports Phoenix traceability metadata', () => { 44 + expect(teamPerformance._phoenix).toBeDefined(); 45 + expect(teamPerformance._phoenix.name).toBe('Team Performance'); 46 + expect(teamPerformance._phoenix.risk_tier).toBeTruthy(); 47 + }); 48 + 49 + it('has exported functions', () => { 50 + const exports = Object.keys(teamPerformance).filter(k => k !== '_phoenix'); 51 + expect(exports.length).toBeGreaterThan(0); 52 + }); 53 + }); 54 + 55 + }); 56 + 57 + describe('Analytics server', () => { 58 + const instance = startServer(0); // random port 59 + 60 + afterAll(() => new Promise<void>(resolve => instance.server.close(() => resolve()))); 61 + 62 + it('GET /health returns 200', async () => { 63 + await instance.ready; 64 + const res = await fetch(`http://localhost:${instance.port}/health`); 65 + expect(res.status).toBe(200); 66 + const body = await res.json() as Record<string, unknown>; 67 + expect(body.status).toBe('ok'); 68 + expect(body.service).toBe('Analytics'); 69 + }); 70 + 71 + it('GET /metrics returns request counts', async () => { 72 + await instance.ready; 73 + const res = await fetch(`http://localhost:${instance.port}/metrics`); 74 + expect(res.status).toBe(200); 75 + const body = await res.json() as Record<string, unknown>; 76 + expect(typeof body.requests_total).toBe('number'); 77 + }); 78 + 79 + it('GET /modules lists all registered modules', async () => { 80 + await instance.ready; 81 + const res = await fetch(`http://localhost:${instance.port}/modules`); 82 + expect(res.status).toBe(200); 83 + const body = await res.json() as Array<Record<string, unknown>>; 84 + expect(body.length).toBe(3); 85 + }); 86 + 87 + it('GET /unknown returns 404', async () => { 88 + await instance.ready; 89 + const res = await fetch(`http://localhost:${instance.port}/unknown`); 90 + expect(res.status).toBe(404); 91 + }); 92 + });
+10
examples/taskflow/src/generated/analytics/index.ts
··· 1 + /** 2 + * Analytics 3 + * 4 + * AUTO-GENERATED by Phoenix VCS 5 + * Barrel export for all Analytics modules. 6 + */ 7 + 8 + export * as metrics from './metrics.js'; 9 + export * as priorityBreakdown from './priority-breakdown.js'; 10 + export * as teamPerformance from './team-performance.js';
+148
examples/taskflow/src/generated/analytics/metrics.ts
··· 1 + export interface TaskRecord { 2 + id: string; 3 + createdAt: Date; 4 + completedAt?: Date; 5 + dueDate?: Date; 6 + status: 'pending' | 'in-progress' | 'completed' | 'cancelled'; 7 + } 8 + 9 + export interface MetricsSnapshot { 10 + totalTasksCreated: number; 11 + totalTasksCompleted: number; 12 + totalTasksOverdue: number; 13 + averageCompletionTimeHours: number; 14 + throughputTasksPerDay: number; 15 + calculatedAt: Date; 16 + } 17 + 18 + export class Metrics { 19 + private tasks: TaskRecord[] = []; 20 + 21 + constructor(tasks: TaskRecord[] = []) { 22 + this.tasks = [...tasks]; 23 + } 24 + 25 + updateTasks(tasks: TaskRecord[]): void { 26 + this.tasks = [...tasks]; 27 + } 28 + 29 + addTask(task: TaskRecord): void { 30 + this.tasks.push(task); 31 + } 32 + 33 + removeTask(taskId: string): void { 34 + this.tasks = this.tasks.filter(task => task.id !== taskId); 35 + } 36 + 37 + calculateMetrics(): MetricsSnapshot { 38 + const now = new Date(); 39 + const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); 40 + 41 + const totalTasksCreated = this.tasks.length; 42 + 43 + const completedTasks = this.tasks.filter(task => task.status === 'completed' && task.completedAt); 44 + const totalTasksCompleted = completedTasks.length; 45 + 46 + const overdueTasks = this.tasks.filter(task => { 47 + if (!task.dueDate || task.status === 'completed' || task.status === 'cancelled') { 48 + return false; 49 + } 50 + return now > task.dueDate; 51 + }); 52 + const totalTasksOverdue = overdueTasks.length; 53 + 54 + const averageCompletionTimeHours = this.calculateAverageCompletionTime(completedTasks); 55 + 56 + const throughputTasksPerDay = this.calculateThroughput(completedTasks, sevenDaysAgo, now); 57 + 58 + return { 59 + totalTasksCreated, 60 + totalTasksCompleted, 61 + totalTasksOverdue, 62 + averageCompletionTimeHours, 63 + throughputTasksPerDay, 64 + calculatedAt: now 65 + }; 66 + } 67 + 68 + private calculateAverageCompletionTime(completedTasks: TaskRecord[]): number { 69 + if (completedTasks.length === 0) { 70 + return 0; 71 + } 72 + 73 + const totalCompletionTimeMs = completedTasks.reduce((sum, task) => { 74 + if (!task.completedAt) { 75 + return sum; 76 + } 77 + const completionTimeMs = task.completedAt.getTime() - task.createdAt.getTime(); 78 + return sum + completionTimeMs; 79 + }, 0); 80 + 81 + const averageCompletionTimeMs = totalCompletionTimeMs / completedTasks.length; 82 + return averageCompletionTimeMs / (1000 * 60 * 60); // Convert to hours 83 + } 84 + 85 + private calculateThroughput(completedTasks: TaskRecord[], startDate: Date, endDate: Date): number { 86 + const tasksCompletedInWindow = completedTasks.filter(task => { 87 + if (!task.completedAt) { 88 + return false; 89 + } 90 + return task.completedAt >= startDate && task.completedAt <= endDate; 91 + }); 92 + 93 + const windowDays = (endDate.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24); 94 + 95 + if (windowDays === 0) { 96 + return 0; 97 + } 98 + 99 + return tasksCompletedInWindow.length / windowDays; 100 + } 101 + 102 + getTotalTasksCreated(): number { 103 + return this.tasks.length; 104 + } 105 + 106 + getTotalTasksCompleted(): number { 107 + return this.tasks.filter(task => task.status === 'completed').length; 108 + } 109 + 110 + getTotalTasksOverdue(): number { 111 + const now = new Date(); 112 + return this.tasks.filter(task => { 113 + if (!task.dueDate || task.status === 'completed' || task.status === 'cancelled') { 114 + return false; 115 + } 116 + return now > task.dueDate; 117 + }).length; 118 + } 119 + 120 + getAverageCompletionTimeHours(): number { 121 + const completedTasks = this.tasks.filter(task => task.status === 'completed' && task.completedAt); 122 + return this.calculateAverageCompletionTime(completedTasks); 123 + } 124 + 125 + getThroughputTasksPerDay(): number { 126 + const now = new Date(); 127 + const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); 128 + const completedTasks = this.tasks.filter(task => task.status === 'completed' && task.completedAt); 129 + return this.calculateThroughput(completedTasks, sevenDaysAgo, now); 130 + } 131 + } 132 + 133 + export function createMetrics(tasks: TaskRecord[] = []): Metrics { 134 + return new Metrics(tasks); 135 + } 136 + 137 + export function calculateMetricsFromTasks(tasks: TaskRecord[]): MetricsSnapshot { 138 + const metrics = new Metrics(tasks); 139 + return metrics.calculateMetrics(); 140 + } 141 + 142 + /** @internal Phoenix VCS traceability — do not remove. */ 143 + export const _phoenix = { 144 + iu_id: 'c6a76ccf723cbaf85f990a1b7260b82c8063e5b48c1ef23fcbcc42161e235cbd', 145 + name: 'Metrics', 146 + risk_tier: 'high', 147 + canon_ids: [4 as const], 148 + } as const;
+154
examples/taskflow/src/generated/analytics/priority-breakdown.ts
··· 1 + export interface Task { 2 + id: string; 3 + priority: 'low' | 'medium' | 'high' | 'critical'; 4 + status: 'pending' | 'in-progress' | 'completed' | 'blocked' | 'cancelled'; 5 + } 6 + 7 + export interface PriorityBreakdownItem { 8 + priority: string; 9 + count: number; 10 + percentage: number; 11 + } 12 + 13 + export interface StatusBreakdownItem { 14 + status: string; 15 + count: number; 16 + percentage: number; 17 + } 18 + 19 + export interface BreakdownReport { 20 + priorityBreakdown: PriorityBreakdownItem[]; 21 + statusBreakdown: StatusBreakdownItem[]; 22 + totalTasks: number; 23 + } 24 + 25 + export class PriorityBreakdown { 26 + private tasks: Task[] = []; 27 + 28 + addTask(task: Task): void { 29 + this.tasks.push(task); 30 + } 31 + 32 + addTasks(tasks: Task[]): void { 33 + this.tasks.push(...tasks); 34 + } 35 + 36 + removeTask(taskId: string): boolean { 37 + const initialLength = this.tasks.length; 38 + this.tasks = this.tasks.filter(task => task.id !== taskId); 39 + return this.tasks.length < initialLength; 40 + } 41 + 42 + updateTask(taskId: string, updates: Partial<Pick<Task, 'priority' | 'status'>>): boolean { 43 + const task = this.tasks.find(t => t.id === taskId); 44 + if (!task) return false; 45 + 46 + if (updates.priority) task.priority = updates.priority; 47 + if (updates.status) task.status = updates.status; 48 + return true; 49 + } 50 + 51 + clearTasks(): void { 52 + this.tasks = []; 53 + } 54 + 55 + generateReport(): BreakdownReport { 56 + const totalTasks = this.tasks.length; 57 + 58 + const priorityBreakdown = this.calculatePriorityBreakdown(totalTasks); 59 + const statusBreakdown = this.calculateStatusBreakdown(totalTasks); 60 + 61 + return { 62 + priorityBreakdown, 63 + statusBreakdown, 64 + totalTasks 65 + }; 66 + } 67 + 68 + private calculatePriorityBreakdown(totalTasks: number): PriorityBreakdownItem[] { 69 + const priorityCounts = new Map<string, number>(); 70 + const priorities = ['low', 'medium', 'high', 'critical']; 71 + 72 + // Initialize all priorities with 0 73 + priorities.forEach(priority => priorityCounts.set(priority, 0)); 74 + 75 + // Count tasks by priority 76 + this.tasks.forEach(task => { 77 + const current = priorityCounts.get(task.priority) || 0; 78 + priorityCounts.set(task.priority, current + 1); 79 + }); 80 + 81 + return priorities.map(priority => { 82 + const count = priorityCounts.get(priority) || 0; 83 + const percentage = totalTasks > 0 ? Math.round((count / totalTasks) * 100 * 100) / 100 : 0; 84 + 85 + return { 86 + priority, 87 + count, 88 + percentage 89 + }; 90 + }); 91 + } 92 + 93 + private calculateStatusBreakdown(totalTasks: number): StatusBreakdownItem[] { 94 + const statusCounts = new Map<string, number>(); 95 + const statuses = ['pending', 'in-progress', 'completed', 'blocked', 'cancelled']; 96 + 97 + // Initialize all statuses with 0 98 + statuses.forEach(status => statusCounts.set(status, 0)); 99 + 100 + // Count tasks by status 101 + this.tasks.forEach(task => { 102 + const current = statusCounts.get(task.status) || 0; 103 + statusCounts.set(task.status, current + 1); 104 + }); 105 + 106 + return statuses.map(status => { 107 + const count = statusCounts.get(status) || 0; 108 + const percentage = totalTasks > 0 ? Math.round((count / totalTasks) * 100 * 100) / 100 : 0; 109 + 110 + return { 111 + status, 112 + count, 113 + percentage 114 + }; 115 + }); 116 + } 117 + 118 + getTasks(): readonly Task[] { 119 + return [...this.tasks]; 120 + } 121 + 122 + getTaskCount(): number { 123 + return this.tasks.length; 124 + } 125 + 126 + getTasksByPriority(priority: Task['priority']): Task[] { 127 + return this.tasks.filter(task => task.priority === priority); 128 + } 129 + 130 + getTasksByStatus(status: Task['status']): Task[] { 131 + return this.tasks.filter(task => task.status === status); 132 + } 133 + } 134 + 135 + export function createPriorityBreakdown(initialTasks: Task[] = []): PriorityBreakdown { 136 + const breakdown = new PriorityBreakdown(); 137 + if (initialTasks.length > 0) { 138 + breakdown.addTasks(initialTasks); 139 + } 140 + return breakdown; 141 + } 142 + 143 + export function generateQuickReport(tasks: Task[]): BreakdownReport { 144 + const breakdown = createPriorityBreakdown(tasks); 145 + return breakdown.generateReport(); 146 + } 147 + 148 + /** @internal Phoenix VCS traceability — do not remove. */ 149 + export const _phoenix = { 150 + iu_id: '0e8c6fd7f3c15acb3e984fe7b4acac3476c2cb0abade85705720eaf362f1cca9', 151 + name: 'Priority Breakdown', 152 + risk_tier: 'low', 153 + canon_ids: [3 as const], 154 + } as const;
+123
examples/taskflow/src/generated/analytics/server.ts
··· 1 + /** 2 + * Analytics — HTTP Server 3 + * 4 + * AUTO-GENERATED by Phoenix VCS 5 + * Provides health check, metrics, and module endpoints. 6 + */ 7 + 8 + import { createServer, IncomingMessage, ServerResponse } from 'node:http'; 9 + 10 + import * as metrics from './metrics.js'; 11 + import * as priorityBreakdown from './priority-breakdown.js'; 12 + import * as teamPerformance from './team-performance.js'; 13 + 14 + // ─── Metrics ───────────────────────────────────────────────────────────────── 15 + 16 + const _svcMetrics = { 17 + requests_total: 0, 18 + requests_by_path: {} as Record<string, number>, 19 + errors_total: 0, 20 + uptime_start: Date.now(), 21 + }; 22 + 23 + // ─── Module Registry ───────────────────────────────────────────────────────── 24 + 25 + const _svcModules = { 26 + 'metrics': metrics, 27 + 'priority-breakdown': priorityBreakdown, 28 + 'team-performance': teamPerformance, 29 + }; 30 + 31 + // ─── Router ────────────────────────────────────────────────────────────────── 32 + 33 + type Handler = (req: IncomingMessage, res: ServerResponse) => void | Promise<void>; 34 + 35 + const routes: Record<string, Handler> = { 36 + '/health': (_req, res) => { 37 + res.writeHead(200, { 'Content-Type': 'application/json' }); 38 + res.end(JSON.stringify({ 39 + status: 'ok', 40 + service: 'Analytics', 41 + uptime: Math.floor((Date.now() - _svcMetrics.uptime_start) / 1000), 42 + modules: Object.keys(_svcModules), 43 + })); 44 + }, 45 + 46 + '/metrics': (_req, res) => { 47 + res.writeHead(200, { 'Content-Type': 'application/json' }); 48 + res.end(JSON.stringify({ 49 + ..._svcMetrics, 50 + uptime_seconds: Math.floor((Date.now() - _svcMetrics.uptime_start) / 1000), 51 + }, null, 2)); 52 + }, 53 + 54 + '/modules': (_req, res) => { 55 + const info = Object.entries(_svcModules).map(([name, mod]) => { 56 + const phoenix = (mod as Record<string, unknown>)._phoenix as Record<string, unknown> | undefined; 57 + return { 58 + name, 59 + risk_tier: phoenix?.risk_tier ?? 'unknown', 60 + exports: Object.keys(mod).filter(k => k !== '_phoenix'), 61 + }; 62 + }); 63 + res.writeHead(200, { 'Content-Type': 'application/json' }); 64 + res.end(JSON.stringify(info, null, 2)); 65 + }, 66 + }; 67 + 68 + // ─── Server ────────────────────────────────────────────────────────────────── 69 + 70 + function handleRequest(req: IncomingMessage, res: ServerResponse): void { 71 + const url = req.url ?? '/'; 72 + const path = url.split('?')[0]; 73 + 74 + _svcMetrics.requests_total++; 75 + _svcMetrics.requests_by_path[path] = (_svcMetrics.requests_by_path[path] ?? 0) + 1; 76 + 77 + const handler = routes[path]; 78 + if (handler) { 79 + try { 80 + handler(req, res); 81 + } catch (err) { 82 + _svcMetrics.errors_total++; 83 + res.writeHead(500, { 'Content-Type': 'application/json' }); 84 + res.end(JSON.stringify({ error: String(err) })); 85 + } 86 + } else { 87 + res.writeHead(404, { 'Content-Type': 'application/json' }); 88 + res.end(JSON.stringify({ 89 + error: 'Not Found', 90 + path, 91 + available: Object.keys(routes), 92 + })); 93 + } 94 + } 95 + 96 + export function startServer(port?: number): { server: ReturnType<typeof createServer>; port: number; ready: Promise<void> } { 97 + const requestedPort = port ?? parseInt(process.env.ANALYTICS_PORT ?? process.env.PORT ?? '3000', 10); 98 + const server = createServer(handleRequest); 99 + let actualPort = requestedPort; 100 + 101 + const ready = new Promise<void>(resolve => { 102 + server.listen(requestedPort, () => { 103 + const addr = server.address(); 104 + if (addr && typeof addr === 'object') actualPort = addr.port; 105 + result.port = actualPort; 106 + console.log(`Analytics listening on http://localhost:${actualPort}`); 107 + console.log(` /health — health check`); 108 + console.log(` /metrics — request metrics`); 109 + console.log(` /modules — registered modules`); 110 + resolve(); 111 + }); 112 + }); 113 + 114 + const result = { server, port: actualPort, ready }; 115 + return result; 116 + } 117 + 118 + // Start when run directly 119 + const isMain = process.argv[1]?.endsWith('/analytics/server.js') || 120 + process.argv[1]?.endsWith('/analytics/server.ts'); 121 + if (isMain) { 122 + startServer(); 123 + }
+99
examples/taskflow/src/generated/analytics/team-performance.ts
··· 1 + export interface Task { 2 + id: string; 3 + assignee?: string; 4 + status: 'pending' | 'in-progress' | 'done' | 'cancelled'; 5 + } 6 + 7 + export interface AssigneePerformance { 8 + assignee: string; 9 + totalAssigned: number; 10 + completed: number; 11 + completionRate: number; 12 + } 13 + 14 + export interface TeamPerformanceMetrics { 15 + assigneePerformance: AssigneePerformance[]; 16 + topPerformer: AssigneePerformance | null; 17 + } 18 + 19 + export class TeamPerformanceCalculator { 20 + calculateTeamPerformance(tasks: Task[]): TeamPerformanceMetrics { 21 + const assignedTasks = tasks.filter(task => task.assignee !== undefined); 22 + 23 + const assigneeStats = new Map<string, { total: number; completed: number }>(); 24 + 25 + for (const task of assignedTasks) { 26 + const assignee = task.assignee!; 27 + const stats = assigneeStats.get(assignee) || { total: 0, completed: 0 }; 28 + 29 + stats.total++; 30 + if (task.status === 'done') { 31 + stats.completed++; 32 + } 33 + 34 + assigneeStats.set(assignee, stats); 35 + } 36 + 37 + const assigneePerformance: AssigneePerformance[] = Array.from(assigneeStats.entries()) 38 + .map(([assignee, stats]) => ({ 39 + assignee, 40 + totalAssigned: stats.total, 41 + completed: stats.completed, 42 + completionRate: stats.total > 0 ? stats.completed / stats.total : 0 43 + })) 44 + .sort((a, b) => b.completionRate - a.completionRate); 45 + 46 + const topPerformer = this.identifyTopPerformer(assigneePerformance); 47 + 48 + return { 49 + assigneePerformance, 50 + topPerformer 51 + }; 52 + } 53 + 54 + private identifyTopPerformer(performance: AssigneePerformance[]): AssigneePerformance | null { 55 + const eligiblePerformers = performance.filter(p => p.totalAssigned >= 3); 56 + 57 + if (eligiblePerformers.length === 0) { 58 + return null; 59 + } 60 + 61 + return eligiblePerformers.reduce((top, current) => 62 + current.completionRate > top.completionRate ? current : top 63 + ); 64 + } 65 + 66 + getCompletionRate(assignee: string, tasks: Task[]): number { 67 + const assigneeTasks = tasks.filter(task => task.assignee === assignee); 68 + 69 + if (assigneeTasks.length === 0) { 70 + return 0; 71 + } 72 + 73 + const completedTasks = assigneeTasks.filter(task => task.status === 'done'); 74 + return completedTasks.length / assigneeTasks.length; 75 + } 76 + } 77 + 78 + export function calculateTeamPerformance(tasks: Task[]): TeamPerformanceMetrics { 79 + const calculator = new TeamPerformanceCalculator(); 80 + return calculator.calculateTeamPerformance(tasks); 81 + } 82 + 83 + export function getTopPerformer(tasks: Task[]): AssigneePerformance | null { 84 + const metrics = calculateTeamPerformance(tasks); 85 + return metrics.topPerformer; 86 + } 87 + 88 + export function getAssigneeCompletionRate(assignee: string, tasks: Task[]): number { 89 + const calculator = new TeamPerformanceCalculator(); 90 + return calculator.getCompletionRate(assignee, tasks); 91 + } 92 + 93 + /** @internal Phoenix VCS traceability — do not remove. */ 94 + export const _phoenix = { 95 + iu_id: '1a341b16d06c4b94fd080e8175eac6e46413420cc871dc0b1183196ced852b25', 96 + name: 'Team Performance', 97 + risk_tier: 'high', 98 + canon_ids: [3 as const], 99 + } as const;
+13
examples/taskflow/src/generated/index.ts
··· 1 + /** 2 + * Phoenix VCS — Generated Service Registry 3 + * 4 + * AUTO-GENERATED by Phoenix VCS 5 + */ 6 + 7 + export * as analytics from './analytics/index.js'; 8 + export * as tasks from './tasks/index.js'; 9 + 10 + export const services = [ 11 + { name: 'Analytics', dir: 'analytics', port: 3000, modules: 3 }, 12 + { name: 'Tasks', dir: 'tasks', port: 3001, modules: 4 }, 13 + ] as const;
+106
examples/taskflow/src/generated/tasks/__tests__/tasks.test.ts
··· 1 + /** 2 + * Tasks — Generated Tests 3 + * 4 + * AUTO-GENERATED by Phoenix VCS 5 + * Tests module structure, server health, and Phoenix traceability. 6 + */ 7 + 8 + import { describe, it, expect, afterAll } from 'vitest'; 9 + import { startServer } from '../server.js'; 10 + 11 + import * as assignment from '../assignment.js'; 12 + import * as deadlineManagement from '../deadline-management.js'; 13 + import * as searchAndFiltering from '../search-and-filtering.js'; 14 + import * as taskLifecycle from '../task-lifecycle.js'; 15 + 16 + describe('Tasks modules', () => { 17 + describe('Assignment', () => { 18 + it('exports Phoenix traceability metadata', () => { 19 + expect(assignment._phoenix).toBeDefined(); 20 + expect(assignment._phoenix.name).toBe('Assignment'); 21 + expect(assignment._phoenix.risk_tier).toBeTruthy(); 22 + }); 23 + 24 + it('has exported functions', () => { 25 + const exports = Object.keys(assignment).filter(k => k !== '_phoenix'); 26 + expect(exports.length).toBeGreaterThan(0); 27 + }); 28 + }); 29 + 30 + describe('Deadline Management', () => { 31 + it('exports Phoenix traceability metadata', () => { 32 + expect(deadlineManagement._phoenix).toBeDefined(); 33 + expect(deadlineManagement._phoenix.name).toBe('Deadline Management'); 34 + expect(deadlineManagement._phoenix.risk_tier).toBeTruthy(); 35 + }); 36 + 37 + it('has exported functions', () => { 38 + const exports = Object.keys(deadlineManagement).filter(k => k !== '_phoenix'); 39 + expect(exports.length).toBeGreaterThan(0); 40 + }); 41 + }); 42 + 43 + describe('Search and Filtering', () => { 44 + it('exports Phoenix traceability metadata', () => { 45 + expect(searchAndFiltering._phoenix).toBeDefined(); 46 + expect(searchAndFiltering._phoenix.name).toBe('Search and Filtering'); 47 + expect(searchAndFiltering._phoenix.risk_tier).toBeTruthy(); 48 + }); 49 + 50 + it('has exported functions', () => { 51 + const exports = Object.keys(searchAndFiltering).filter(k => k !== '_phoenix'); 52 + expect(exports.length).toBeGreaterThan(0); 53 + }); 54 + }); 55 + 56 + describe('Task Lifecycle', () => { 57 + it('exports Phoenix traceability metadata', () => { 58 + expect(taskLifecycle._phoenix).toBeDefined(); 59 + expect(taskLifecycle._phoenix.name).toBe('Task Lifecycle'); 60 + expect(taskLifecycle._phoenix.risk_tier).toBeTruthy(); 61 + }); 62 + 63 + it('has exported functions', () => { 64 + const exports = Object.keys(taskLifecycle).filter(k => k !== '_phoenix'); 65 + expect(exports.length).toBeGreaterThan(0); 66 + }); 67 + }); 68 + 69 + }); 70 + 71 + describe('Tasks server', () => { 72 + const instance = startServer(0); // random port 73 + 74 + afterAll(() => new Promise<void>(resolve => instance.server.close(() => resolve()))); 75 + 76 + it('GET /health returns 200', async () => { 77 + await instance.ready; 78 + const res = await fetch(`http://localhost:${instance.port}/health`); 79 + expect(res.status).toBe(200); 80 + const body = await res.json() as Record<string, unknown>; 81 + expect(body.status).toBe('ok'); 82 + expect(body.service).toBe('Tasks'); 83 + }); 84 + 85 + it('GET /metrics returns request counts', async () => { 86 + await instance.ready; 87 + const res = await fetch(`http://localhost:${instance.port}/metrics`); 88 + expect(res.status).toBe(200); 89 + const body = await res.json() as Record<string, unknown>; 90 + expect(typeof body.requests_total).toBe('number'); 91 + }); 92 + 93 + it('GET /modules lists all registered modules', async () => { 94 + await instance.ready; 95 + const res = await fetch(`http://localhost:${instance.port}/modules`); 96 + expect(res.status).toBe(200); 97 + const body = await res.json() as Array<Record<string, unknown>>; 98 + expect(body.length).toBe(4); 99 + }); 100 + 101 + it('GET /unknown returns 404', async () => { 102 + await instance.ready; 103 + const res = await fetch(`http://localhost:${instance.port}/unknown`); 104 + expect(res.status).toBe(404); 105 + }); 106 + });
+25
examples/taskflow/src/generated/tasks/assignment.ts
··· 1 + /** 2 + * Assignment 3 + * 4 + * AUTO-GENERATED by Phoenix VCS — DO NOT EDIT DIRECTLY 5 + * Risk Tier: high 6 + */ 7 + 8 + /** Placeholder type — replace with your domain model. */ 9 + export type PreviousAssignee = Record<string, unknown>; 10 + 11 + /** 12 + * Reassigning a task must log the previous assignee in an audit trail 13 + */ 14 + export function log(previousAssignee: PreviousAssignee): void { 15 + // TODO: implement 16 + throw new Error('Not implemented: log'); 17 + } 18 + 19 + /** @internal Phoenix VCS traceability — do not remove. */ 20 + export const _phoenix = { 21 + iu_id: 'c59272a317e39a8c65f755776338d447a62410fdfab6bef99784dfb7985e6788', 22 + name: 'Assignment', 23 + risk_tier: 'high', 24 + canon_ids: [4 as const], 25 + } as const;
+214
examples/taskflow/src/generated/tasks/deadline-management.ts
··· 1 + export interface TaskDeadline { 2 + taskId: string; 3 + deadline: Date; 4 + isOverdue: boolean; 5 + daysOverdue: number; 6 + } 7 + 8 + export interface Task { 9 + id: string; 10 + title: string; 11 + description?: string; 12 + deadline?: Date; 13 + completed: boolean; 14 + createdAt: Date; 15 + completedAt?: Date; 16 + } 17 + 18 + export interface DeadlineWarning { 19 + message: string; 20 + taskId: string; 21 + deadline: Date; 22 + severity: 'warning'; 23 + } 24 + 25 + export class DeadlineManager { 26 + private tasks = new Map<string, Task>(); 27 + private warnings: DeadlineWarning[] = []; 28 + 29 + addTask(task: Task): void { 30 + if (task.deadline && this.isDateInPast(task.deadline)) { 31 + const warning: DeadlineWarning = { 32 + message: `Task "${task.title}" has a deadline in the past: ${task.deadline.toISOString()}`, 33 + taskId: task.id, 34 + deadline: task.deadline, 35 + severity: 'warning' 36 + }; 37 + this.warnings.push(warning); 38 + } 39 + 40 + this.tasks.set(task.id, { ...task }); 41 + } 42 + 43 + updateTask(taskId: string, updates: Partial<Task>): void { 44 + const task = this.tasks.get(taskId); 45 + if (!task) { 46 + throw new Error(`Task with id ${taskId} not found`); 47 + } 48 + 49 + const updatedTask = { ...task, ...updates }; 50 + 51 + if (updates.deadline && this.isDateInPast(updates.deadline)) { 52 + const warning: DeadlineWarning = { 53 + message: `Task "${updatedTask.title}" has a deadline in the past: ${updates.deadline.toISOString()}`, 54 + taskId: taskId, 55 + deadline: updates.deadline, 56 + severity: 'warning' 57 + }; 58 + this.warnings.push(warning); 59 + } 60 + 61 + this.tasks.set(taskId, updatedTask); 62 + } 63 + 64 + setDeadline(taskId: string, deadline: Date): void { 65 + const task = this.tasks.get(taskId); 66 + if (!task) { 67 + throw new Error(`Task with id ${taskId} not found`); 68 + } 69 + 70 + if (this.isDateInPast(deadline)) { 71 + const warning: DeadlineWarning = { 72 + message: `Task "${task.title}" has a deadline in the past: ${deadline.toISOString()}`, 73 + taskId: taskId, 74 + deadline: deadline, 75 + severity: 'warning' 76 + }; 77 + this.warnings.push(warning); 78 + } 79 + 80 + this.tasks.set(taskId, { ...task, deadline }); 81 + } 82 + 83 + removeDeadline(taskId: string): void { 84 + const task = this.tasks.get(taskId); 85 + if (!task) { 86 + throw new Error(`Task with id ${taskId} not found`); 87 + } 88 + 89 + const updatedTask = { ...task }; 90 + delete updatedTask.deadline; 91 + this.tasks.set(taskId, updatedTask); 92 + } 93 + 94 + getOverdueTasks(): TaskDeadline[] { 95 + const now = new Date(); 96 + const overdueTasks: TaskDeadline[] = []; 97 + 98 + for (const task of this.tasks.values()) { 99 + if (task.deadline && !task.completed && task.deadline < now) { 100 + const daysOverdue = Math.ceil((now.getTime() - task.deadline.getTime()) / (1000 * 60 * 60 * 24)); 101 + 102 + overdueTasks.push({ 103 + taskId: task.id, 104 + deadline: task.deadline, 105 + isOverdue: true, 106 + daysOverdue 107 + }); 108 + } 109 + } 110 + 111 + return overdueTasks.sort((a, b) => b.daysOverdue - a.daysOverdue); 112 + } 113 + 114 + getTasksWithDeadlines(): TaskDeadline[] { 115 + const now = new Date(); 116 + const tasksWithDeadlines: TaskDeadline[] = []; 117 + 118 + for (const task of this.tasks.values()) { 119 + if (task.deadline) { 120 + const isOverdue = !task.completed && task.deadline < now; 121 + const daysOverdue = isOverdue 122 + ? Math.ceil((now.getTime() - task.deadline.getTime()) / (1000 * 60 * 60 * 24)) 123 + : 0; 124 + 125 + tasksWithDeadlines.push({ 126 + taskId: task.id, 127 + deadline: task.deadline, 128 + isOverdue, 129 + daysOverdue 130 + }); 131 + } 132 + } 133 + 134 + return tasksWithDeadlines.sort((a, b) => a.deadline.getTime() - b.deadline.getTime()); 135 + } 136 + 137 + getTask(taskId: string): Task | undefined { 138 + return this.tasks.get(taskId); 139 + } 140 + 141 + getAllTasks(): Task[] { 142 + return Array.from(this.tasks.values()); 143 + } 144 + 145 + getWarnings(): DeadlineWarning[] { 146 + return [...this.warnings]; 147 + } 148 + 149 + clearWarnings(): void { 150 + this.warnings = []; 151 + } 152 + 153 + isTaskOverdue(taskId: string): boolean { 154 + const task = this.tasks.get(taskId); 155 + if (!task || !task.deadline || task.completed) { 156 + return false; 157 + } 158 + 159 + return task.deadline < new Date(); 160 + } 161 + 162 + private isDateInPast(date: Date): boolean { 163 + const now = new Date(); 164 + return date < now; 165 + } 166 + } 167 + 168 + export function createDeadlineManager(): DeadlineManager { 169 + return new DeadlineManager(); 170 + } 171 + 172 + export function isOverdue(deadline: Date, completed: boolean): boolean { 173 + if (completed) { 174 + return false; 175 + } 176 + return deadline < new Date(); 177 + } 178 + 179 + export function calculateDaysOverdue(deadline: Date): number { 180 + const now = new Date(); 181 + if (deadline >= now) { 182 + return 0; 183 + } 184 + return Math.ceil((now.getTime() - deadline.getTime()) / (1000 * 60 * 60 * 24)); 185 + } 186 + 187 + export function formatDeadlineStatus(deadline: Date, completed: boolean): string { 188 + if (completed) { 189 + return 'Completed'; 190 + } 191 + 192 + const now = new Date(); 193 + if (deadline < now) { 194 + const daysOverdue = calculateDaysOverdue(deadline); 195 + return `Overdue by ${daysOverdue} day${daysOverdue === 1 ? '' : 's'}`; 196 + } 197 + 198 + const daysUntil = Math.ceil((deadline.getTime() - now.getTime()) / (1000 * 60 * 60 * 24)); 199 + if (daysUntil === 0) { 200 + return 'Due today'; 201 + } else if (daysUntil === 1) { 202 + return 'Due tomorrow'; 203 + } else { 204 + return `Due in ${daysUntil} days`; 205 + } 206 + } 207 + 208 + /** @internal Phoenix VCS traceability — do not remove. */ 209 + export const _phoenix = { 210 + iu_id: '8e0191994ba2a364acf2753ba76a9c63e725dd7b9a4993792ccf1d62806eb460', 211 + name: 'Deadline Management', 212 + risk_tier: 'high', 213 + canon_ids: [4 as const], 214 + } as const;
+11
examples/taskflow/src/generated/tasks/index.ts
··· 1 + /** 2 + * Tasks 3 + * 4 + * AUTO-GENERATED by Phoenix VCS 5 + * Barrel export for all Tasks modules. 6 + */ 7 + 8 + export * as assignment from './assignment.js'; 9 + export * as deadlineManagement from './deadline-management.js'; 10 + export * as searchAndFiltering from './search-and-filtering.js'; 11 + export * as taskLifecycle from './task-lifecycle.js';
+153
examples/taskflow/src/generated/tasks/search-and-filtering.ts
··· 1 + export interface Task { 2 + id: string; 3 + title: string; 4 + priority: 'critical' | 'high' | 'medium' | 'low'; 5 + created_at: Date; 6 + description?: string; 7 + status?: string; 8 + assignee?: string; 9 + deadline?: Date; 10 + } 11 + 12 + export interface SearchOptions { 13 + query?: string; 14 + sortBy?: 'priority' | 'created_at'; 15 + sortOrder?: 'asc' | 'desc'; 16 + } 17 + 18 + export interface SearchResult { 19 + tasks: Task[]; 20 + total: number; 21 + query: string; 22 + } 23 + 24 + const PRIORITY_ORDER: Record<Task['priority'], number> = { 25 + critical: 0, 26 + high: 1, 27 + medium: 2, 28 + low: 3, 29 + }; 30 + 31 + export class TaskSearchEngine { 32 + private tasks: Task[] = []; 33 + 34 + constructor(initialTasks: Task[] = []) { 35 + this.tasks = [...initialTasks]; 36 + } 37 + 38 + addTask(task: Task): void { 39 + this.tasks.push(task); 40 + } 41 + 42 + addTasks(tasks: Task[]): void { 43 + this.tasks.push(...tasks); 44 + } 45 + 46 + removeTask(taskId: string): boolean { 47 + const initialLength = this.tasks.length; 48 + this.tasks = this.tasks.filter(task => task.id !== taskId); 49 + return this.tasks.length < initialLength; 50 + } 51 + 52 + updateTask(taskId: string, updates: Partial<Task>): boolean { 53 + const taskIndex = this.tasks.findIndex(task => task.id === taskId); 54 + if (taskIndex === -1) { 55 + return false; 56 + } 57 + this.tasks[taskIndex] = { ...this.tasks[taskIndex], ...updates }; 58 + return true; 59 + } 60 + 61 + search(options: SearchOptions = {}): SearchResult { 62 + const { query = '', sortBy = 'priority', sortOrder = 'asc' } = options; 63 + 64 + let filteredTasks = this.tasks; 65 + 66 + // Filter by title substring (case-insensitive) 67 + if (query.trim()) { 68 + const searchQuery = query.toLowerCase().trim(); 69 + filteredTasks = this.tasks.filter(task => 70 + task.title.toLowerCase().includes(searchQuery) 71 + ); 72 + } 73 + 74 + // Sort results 75 + const sortedTasks = this.sortTasks(filteredTasks, sortBy, sortOrder); 76 + 77 + return { 78 + tasks: sortedTasks, 79 + total: sortedTasks.length, 80 + query: query.trim(), 81 + }; 82 + } 83 + 84 + private sortTasks(tasks: Task[], sortBy: string, sortOrder: string): Task[] { 85 + return [...tasks].sort((a, b) => { 86 + let comparison = 0; 87 + 88 + if (sortBy === 'priority') { 89 + // Critical first, then by priority order 90 + comparison = PRIORITY_ORDER[a.priority] - PRIORITY_ORDER[b.priority]; 91 + 92 + // If priorities are equal, sort by created_at as secondary 93 + if (comparison === 0) { 94 + comparison = a.created_at.getTime() - b.created_at.getTime(); 95 + } 96 + } else if (sortBy === 'created_at') { 97 + comparison = a.created_at.getTime() - b.created_at.getTime(); 98 + } 99 + 100 + return sortOrder === 'desc' ? -comparison : comparison; 101 + }); 102 + } 103 + 104 + getAllTasks(): Task[] { 105 + return [...this.tasks]; 106 + } 107 + 108 + getTaskById(taskId: string): Task | undefined { 109 + return this.tasks.find(task => task.id === taskId); 110 + } 111 + 112 + clear(): void { 113 + this.tasks = []; 114 + } 115 + } 116 + 117 + export function searchTasks(tasks: Task[], query: string = ''): SearchResult { 118 + const engine = new TaskSearchEngine(tasks); 119 + return engine.search({ query }); 120 + } 121 + 122 + export function filterTasksByTitle(tasks: Task[], titleSubstring: string): Task[] { 123 + if (!titleSubstring.trim()) { 124 + return [...tasks]; 125 + } 126 + 127 + const searchTerm = titleSubstring.toLowerCase().trim(); 128 + return tasks.filter(task => 129 + task.title.toLowerCase().includes(searchTerm) 130 + ); 131 + } 132 + 133 + export function sortTasksByPriorityAndDate(tasks: Task[]): Task[] { 134 + return [...tasks].sort((a, b) => { 135 + // Critical first, then by priority order 136 + const priorityComparison = PRIORITY_ORDER[a.priority] - PRIORITY_ORDER[b.priority]; 137 + 138 + // If priorities are equal, sort by created_at 139 + if (priorityComparison === 0) { 140 + return a.created_at.getTime() - b.created_at.getTime(); 141 + } 142 + 143 + return priorityComparison; 144 + }); 145 + } 146 + 147 + /** @internal Phoenix VCS traceability — do not remove. */ 148 + export const _phoenix = { 149 + iu_id: 'c5a64a4b957874299fc6c649df80efbce9816db7de3a14d681c952f1345c8802', 150 + name: 'Search and Filtering', 151 + risk_tier: 'low', 152 + canon_ids: [3 as const], 153 + } as const;
+125
examples/taskflow/src/generated/tasks/server.ts
··· 1 + /** 2 + * Tasks — HTTP Server 3 + * 4 + * AUTO-GENERATED by Phoenix VCS 5 + * Provides health check, metrics, and module endpoints. 6 + */ 7 + 8 + import { createServer, IncomingMessage, ServerResponse } from 'node:http'; 9 + 10 + import * as assignment from './assignment.js'; 11 + import * as deadlineManagement from './deadline-management.js'; 12 + import * as searchAndFiltering from './search-and-filtering.js'; 13 + import * as taskLifecycle from './task-lifecycle.js'; 14 + 15 + // ─── Metrics ───────────────────────────────────────────────────────────────── 16 + 17 + const _svcMetrics = { 18 + requests_total: 0, 19 + requests_by_path: {} as Record<string, number>, 20 + errors_total: 0, 21 + uptime_start: Date.now(), 22 + }; 23 + 24 + // ─── Module Registry ───────────────────────────────────────────────────────── 25 + 26 + const _svcModules = { 27 + 'assignment': assignment, 28 + 'deadline-management': deadlineManagement, 29 + 'search-and-filtering': searchAndFiltering, 30 + 'task-lifecycle': taskLifecycle, 31 + }; 32 + 33 + // ─── Router ────────────────────────────────────────────────────────────────── 34 + 35 + type Handler = (req: IncomingMessage, res: ServerResponse) => void | Promise<void>; 36 + 37 + const routes: Record<string, Handler> = { 38 + '/health': (_req, res) => { 39 + res.writeHead(200, { 'Content-Type': 'application/json' }); 40 + res.end(JSON.stringify({ 41 + status: 'ok', 42 + service: 'Tasks', 43 + uptime: Math.floor((Date.now() - _svcMetrics.uptime_start) / 1000), 44 + modules: Object.keys(_svcModules), 45 + })); 46 + }, 47 + 48 + '/metrics': (_req, res) => { 49 + res.writeHead(200, { 'Content-Type': 'application/json' }); 50 + res.end(JSON.stringify({ 51 + ..._svcMetrics, 52 + uptime_seconds: Math.floor((Date.now() - _svcMetrics.uptime_start) / 1000), 53 + }, null, 2)); 54 + }, 55 + 56 + '/modules': (_req, res) => { 57 + const info = Object.entries(_svcModules).map(([name, mod]) => { 58 + const phoenix = (mod as Record<string, unknown>)._phoenix as Record<string, unknown> | undefined; 59 + return { 60 + name, 61 + risk_tier: phoenix?.risk_tier ?? 'unknown', 62 + exports: Object.keys(mod).filter(k => k !== '_phoenix'), 63 + }; 64 + }); 65 + res.writeHead(200, { 'Content-Type': 'application/json' }); 66 + res.end(JSON.stringify(info, null, 2)); 67 + }, 68 + }; 69 + 70 + // ─── Server ────────────────────────────────────────────────────────────────── 71 + 72 + function handleRequest(req: IncomingMessage, res: ServerResponse): void { 73 + const url = req.url ?? '/'; 74 + const path = url.split('?')[0]; 75 + 76 + _svcMetrics.requests_total++; 77 + _svcMetrics.requests_by_path[path] = (_svcMetrics.requests_by_path[path] ?? 0) + 1; 78 + 79 + const handler = routes[path]; 80 + if (handler) { 81 + try { 82 + handler(req, res); 83 + } catch (err) { 84 + _svcMetrics.errors_total++; 85 + res.writeHead(500, { 'Content-Type': 'application/json' }); 86 + res.end(JSON.stringify({ error: String(err) })); 87 + } 88 + } else { 89 + res.writeHead(404, { 'Content-Type': 'application/json' }); 90 + res.end(JSON.stringify({ 91 + error: 'Not Found', 92 + path, 93 + available: Object.keys(routes), 94 + })); 95 + } 96 + } 97 + 98 + export function startServer(port?: number): { server: ReturnType<typeof createServer>; port: number; ready: Promise<void> } { 99 + const requestedPort = port ?? parseInt(process.env.TASKS_PORT ?? process.env.PORT ?? '3001', 10); 100 + const server = createServer(handleRequest); 101 + let actualPort = requestedPort; 102 + 103 + const ready = new Promise<void>(resolve => { 104 + server.listen(requestedPort, () => { 105 + const addr = server.address(); 106 + if (addr && typeof addr === 'object') actualPort = addr.port; 107 + result.port = actualPort; 108 + console.log(`Tasks listening on http://localhost:${actualPort}`); 109 + console.log(` /health — health check`); 110 + console.log(` /metrics — request metrics`); 111 + console.log(` /modules — registered modules`); 112 + resolve(); 113 + }); 114 + }); 115 + 116 + const result = { server, port: actualPort, ready }; 117 + return result; 118 + } 119 + 120 + // Start when run directly 121 + const isMain = process.argv[1]?.endsWith('/tasks/server.js') || 122 + process.argv[1]?.endsWith('/tasks/server.ts'); 123 + if (isMain) { 124 + startServer(); 125 + }
+177
examples/taskflow/src/generated/tasks/task-lifecycle.ts
··· 1 + import { randomUUID } from 'node:crypto'; 2 + 3 + export type TaskStatus = 'open' | 'in_progress' | 'review' | 'done'; 4 + export type TaskPriority = 'low' | 'medium' | 'high' | 'critical'; 5 + 6 + export interface Task { 7 + id: string; 8 + title: string; 9 + description: string; 10 + priority: TaskPriority; 11 + status: TaskStatus; 12 + assignee?: string; 13 + created_at: Date; 14 + updated_at: Date; 15 + completed_at?: Date; 16 + duration_ms?: number; 17 + } 18 + 19 + export interface CreateTaskInput { 20 + title: string; 21 + description: string; 22 + priority: TaskPriority; 23 + assignee?: string; 24 + } 25 + 26 + export interface TaskFilter { 27 + status?: TaskStatus; 28 + priority?: TaskPriority; 29 + assignee?: string; 30 + } 31 + 32 + const VALID_TRANSITIONS: Record<TaskStatus, TaskStatus[]> = { 33 + open: ['in_progress'], 34 + in_progress: ['review', 'open'], 35 + review: ['done', 'in_progress'], 36 + done: [] 37 + }; 38 + 39 + export class TaskLifecycleManager { 40 + private tasks = new Map<string, Task>(); 41 + 42 + createTask(input: CreateTaskInput): Task { 43 + if (!input.title.trim()) { 44 + throw new Error('Task title cannot be empty'); 45 + } 46 + if (!input.description.trim()) { 47 + throw new Error('Task description cannot be empty'); 48 + } 49 + if (!this.isValidPriority(input.priority)) { 50 + throw new Error(`Invalid priority: ${input.priority}. Must be one of: low, medium, high, critical`); 51 + } 52 + 53 + const now = new Date(); 54 + const task: Task = { 55 + id: randomUUID(), 56 + title: input.title.trim(), 57 + description: input.description.trim(), 58 + priority: input.priority, 59 + status: 'open', 60 + assignee: input.assignee?.trim(), 61 + created_at: now, 62 + updated_at: now 63 + }; 64 + 65 + this.tasks.set(task.id, task); 66 + return { ...task }; 67 + } 68 + 69 + getTask(id: string): Task | undefined { 70 + const task = this.tasks.get(id); 71 + return task ? { ...task } : undefined; 72 + } 73 + 74 + updateTaskStatus(id: string, newStatus: TaskStatus): Task { 75 + const task = this.tasks.get(id); 76 + if (!task) { 77 + throw new Error(`Task not found: ${id}`); 78 + } 79 + 80 + if (!this.isValidStatusTransition(task.status, newStatus)) { 81 + throw new Error(`Invalid status transition from ${task.status} to ${newStatus}`); 82 + } 83 + 84 + const now = new Date(); 85 + task.status = newStatus; 86 + task.updated_at = now; 87 + 88 + if (newStatus === 'done' && !task.completed_at) { 89 + task.completed_at = now; 90 + task.duration_ms = now.getTime() - task.created_at.getTime(); 91 + } 92 + 93 + return { ...task }; 94 + } 95 + 96 + updateTask(id: string, updates: Partial<Pick<Task, 'title' | 'description' | 'priority' | 'assignee'>>): Task { 97 + const task = this.tasks.get(id); 98 + if (!task) { 99 + throw new Error(`Task not found: ${id}`); 100 + } 101 + 102 + if (updates.title !== undefined) { 103 + if (!updates.title.trim()) { 104 + throw new Error('Task title cannot be empty'); 105 + } 106 + task.title = updates.title.trim(); 107 + } 108 + 109 + if (updates.description !== undefined) { 110 + if (!updates.description.trim()) { 111 + throw new Error('Task description cannot be empty'); 112 + } 113 + task.description = updates.description.trim(); 114 + } 115 + 116 + if (updates.priority !== undefined) { 117 + if (!this.isValidPriority(updates.priority)) { 118 + throw new Error(`Invalid priority: ${updates.priority}. Must be one of: low, medium, high, critical`); 119 + } 120 + task.priority = updates.priority; 121 + } 122 + 123 + if (updates.assignee !== undefined) { 124 + task.assignee = updates.assignee?.trim(); 125 + } 126 + 127 + task.updated_at = new Date(); 128 + return { ...task }; 129 + } 130 + 131 + filterTasks(filter: TaskFilter): Task[] { 132 + const results: Task[] = []; 133 + 134 + for (const task of this.tasks.values()) { 135 + if (filter.status && task.status !== filter.status) { 136 + continue; 137 + } 138 + if (filter.priority && task.priority !== filter.priority) { 139 + continue; 140 + } 141 + if (filter.assignee && task.assignee !== filter.assignee) { 142 + continue; 143 + } 144 + results.push({ ...task }); 145 + } 146 + 147 + return results; 148 + } 149 + 150 + getAllTasks(): Task[] { 151 + return Array.from(this.tasks.values()).map(task => ({ ...task })); 152 + } 153 + 154 + deleteTask(id: string): boolean { 155 + return this.tasks.delete(id); 156 + } 157 + 158 + private isValidPriority(priority: string): priority is TaskPriority { 159 + return ['low', 'medium', 'high', 'critical'].includes(priority); 160 + } 161 + 162 + private isValidStatusTransition(currentStatus: TaskStatus, newStatus: TaskStatus): boolean { 163 + return VALID_TRANSITIONS[currentStatus].includes(newStatus); 164 + } 165 + } 166 + 167 + export function createTaskLifecycleManager(): TaskLifecycleManager { 168 + return new TaskLifecycleManager(); 169 + } 170 + 171 + /** @internal Phoenix VCS traceability — do not remove. */ 172 + export const _phoenix = { 173 + iu_id: 'ff644f70786609347751a6f701a6ea069ff8a8bb01eb48caa2a15a43b97ce081', 174 + name: 'Task Lifecycle', 175 + risk_tier: 'high', 176 + canon_ids: [8 as const], 177 + } as const;
+23
examples/taskflow/tsconfig.json
··· 1 + { 2 + "compilerOptions": { 3 + "target": "ES2022", 4 + "module": "ESNext", 5 + "moduleResolution": "bundler", 6 + "declaration": true, 7 + "outDir": "dist", 8 + "rootDir": "src", 9 + "strict": true, 10 + "esModuleInterop": true, 11 + "skipLibCheck": true, 12 + "forceConsistentCasingInFileNames": true, 13 + "resolveJsonModule": true, 14 + "sourceMap": true 15 + }, 16 + "include": [ 17 + "src/**/*" 18 + ], 19 + "exclude": [ 20 + "node_modules", 21 + "dist" 22 + ] 23 + }
+7
examples/taskflow/vitest.config.ts
··· 1 + import { defineConfig } from 'vitest/config'; 2 + 3 + export default defineConfig({ 4 + test: { 5 + include: ['src/**/__tests__/**/*.test.ts'], 6 + }, 7 + });
+20 -20
src/scaffold.ts
··· 178 178 // Metrics tracking 179 179 lines.push(`// ─── Metrics ─────────────────────────────────────────────────────────────────`); 180 180 lines.push(''); 181 - lines.push(`const metrics = {`); 181 + lines.push(`const _svcMetrics = {`); 182 182 lines.push(` requests_total: 0,`); 183 183 lines.push(` requests_by_path: {} as Record<string, number>,`); 184 184 lines.push(` errors_total: 0,`); ··· 189 189 // Module registry 190 190 lines.push(`// ─── Module Registry ─────────────────────────────────────────────────────────`); 191 191 lines.push(''); 192 - lines.push(`const modules = {`); 192 + lines.push(`const _svcModules = {`); 193 193 for (const mod of svc.modules) { 194 194 const modName = mod.replace(/\.ts$/, ''); 195 195 const importName = toCamelCase(modName); ··· 209 209 lines.push(` res.end(JSON.stringify({`); 210 210 lines.push(` status: 'ok',`); 211 211 lines.push(` service: '${svc.name}',`); 212 - lines.push(` uptime: Math.floor((Date.now() - metrics.uptime_start) / 1000),`); 213 - lines.push(` modules: Object.keys(modules),`); 212 + lines.push(` uptime: Math.floor((Date.now() - _svcMetrics.uptime_start) / 1000),`); 213 + lines.push(` modules: Object.keys(_svcModules),`); 214 214 lines.push(` }));`); 215 215 lines.push(` },`); 216 216 lines.push(''); 217 217 lines.push(` '/metrics': (_req, res) => {`); 218 218 lines.push(` res.writeHead(200, { 'Content-Type': 'application/json' });`); 219 219 lines.push(` res.end(JSON.stringify({`); 220 - lines.push(` ...metrics,`); 221 - lines.push(` uptime_seconds: Math.floor((Date.now() - metrics.uptime_start) / 1000),`); 220 + lines.push(` ..._svcMetrics,`); 221 + lines.push(` uptime_seconds: Math.floor((Date.now() - _svcMetrics.uptime_start) / 1000),`); 222 222 lines.push(` }, null, 2));`); 223 223 lines.push(` },`); 224 224 lines.push(''); 225 225 lines.push(` '/modules': (_req, res) => {`); 226 - lines.push(` const info = Object.entries(modules).map(([name, mod]) => {`); 226 + lines.push(` const info = Object.entries(_svcModules).map(([name, mod]) => {`); 227 227 lines.push(` const phoenix = (mod as Record<string, unknown>)._phoenix as Record<string, unknown> | undefined;`); 228 228 lines.push(` return {`); 229 229 lines.push(` name,`); ··· 244 244 lines.push(` const url = req.url ?? '/';`); 245 245 lines.push(` const path = url.split('?')[0];`); 246 246 lines.push(''); 247 - lines.push(` metrics.requests_total++;`); 248 - lines.push(` metrics.requests_by_path[path] = (metrics.requests_by_path[path] ?? 0) + 1;`); 247 + lines.push(` _svcMetrics.requests_total++;`); 248 + lines.push(` _svcMetrics.requests_by_path[path] = (_svcMetrics.requests_by_path[path] ?? 0) + 1;`); 249 249 lines.push(''); 250 250 lines.push(` const handler = routes[path];`); 251 251 lines.push(` if (handler) {`); 252 252 lines.push(` try {`); 253 253 lines.push(` handler(req, res);`); 254 254 lines.push(` } catch (err) {`); 255 - lines.push(` metrics.errors_total++;`); 255 + lines.push(` _svcMetrics.errors_total++;`); 256 256 lines.push(` res.writeHead(500, { 'Content-Type': 'application/json' });`); 257 257 lines.push(` res.end(JSON.stringify({ error: String(err) }));`); 258 258 lines.push(` }`); ··· 337 337 // Metrics 338 338 lines.push(`// ─── Metrics ─────────────────────────────────────────────────────────────────`); 339 339 lines.push(''); 340 - lines.push(`const metrics = {`); 340 + lines.push(`const _svcMetrics = {`); 341 341 lines.push(` requests_total: 0,`); 342 342 lines.push(` requests_by_path: {} as Record<string, number>,`); 343 343 lines.push(` errors_total: 0,`); ··· 348 348 // Module registry 349 349 lines.push(`// ─── Module Registry ─────────────────────────────────────────────────────────`); 350 350 lines.push(''); 351 - lines.push(`const modules = {`); 351 + lines.push(`const _svcModules = {`); 352 352 for (const { modName, importName } of imports) { 353 353 lines.push(` '${modName}': ${importName},`); 354 354 } ··· 455 455 lines.push(` res.end(JSON.stringify({`); 456 456 lines.push(` status: 'ok',`); 457 457 lines.push(` service: '${svc.name}',`); 458 - lines.push(` uptime: Math.floor((Date.now() - metrics.uptime_start) / 1000),`); 459 - lines.push(` modules: Object.keys(modules),`); 458 + lines.push(` uptime: Math.floor((Date.now() - _svcMetrics.uptime_start) / 1000),`); 459 + lines.push(` modules: Object.keys(_svcModules),`); 460 460 lines.push(` }));`); 461 461 lines.push(` },`); 462 462 lines.push(''); 463 463 lines.push(` '/metrics': (_req, res) => {`); 464 464 lines.push(` res.writeHead(200, { 'Content-Type': 'application/json' });`); 465 465 lines.push(` res.end(JSON.stringify({`); 466 - lines.push(` ...metrics,`); 467 - lines.push(` uptime_seconds: Math.floor((Date.now() - metrics.uptime_start) / 1000),`); 466 + lines.push(` ..._svcMetrics,`); 467 + lines.push(` uptime_seconds: Math.floor((Date.now() - _svcMetrics.uptime_start) / 1000),`); 468 468 lines.push(` }, null, 2));`); 469 469 lines.push(` },`); 470 470 lines.push(''); 471 471 lines.push(` '/modules': (_req, res) => {`); 472 - lines.push(` const info = Object.entries(modules).map(([name, mod]) => {`); 472 + lines.push(` const info = Object.entries(_svcModules).map(([name, mod]) => {`); 473 473 lines.push(` const phoenix = (mod as Record<string, unknown>)._phoenix as Record<string, unknown> | undefined;`); 474 474 lines.push(` return {`); 475 475 lines.push(` name,`); ··· 490 490 lines.push(` const url = req.url ?? '/';`); 491 491 lines.push(` const path = url.split('?')[0];`); 492 492 lines.push(''); 493 - lines.push(` metrics.requests_total++;`); 494 - lines.push(` metrics.requests_by_path[path] = (metrics.requests_by_path[path] ?? 0) + 1;`); 493 + lines.push(` _svcMetrics.requests_total++;`); 494 + lines.push(` _svcMetrics.requests_by_path[path] = (_svcMetrics.requests_by_path[path] ?? 0) + 1;`); 495 495 lines.push(''); 496 496 lines.push(` const handler = routes[path];`); 497 497 lines.push(` if (handler) {`); 498 498 lines.push(` try {`); 499 499 lines.push(` handler(req, res);`); 500 500 lines.push(` } catch (err) {`); 501 - lines.push(` metrics.errors_total++;`); 501 + lines.push(` _svcMetrics.errors_total++;`); 502 502 lines.push(` res.writeHead(500, { 'Content-Type': 'application/json' });`); 503 503 lines.push(` res.end(JSON.stringify({ error: String(err) }));`); 504 504 lines.push(` }`);