prjct-cli 1.22.0 → 1.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (448) hide show
  1. package/CHANGELOG.md +230 -0
  2. package/bin/prjct +30 -13
  3. package/dist/bin/prjct-core.mjs +1748 -0
  4. package/dist/bin/prjct.mjs +17 -36672
  5. package/dist/cli/linear.mjs +14 -0
  6. package/dist/daemon/entry.mjs +1429 -0
  7. package/dist/templates.json +1 -0
  8. package/package.json +4 -5
  9. package/bin/prjct.ts +0 -342
  10. package/core/__tests__/agentic/analysis-injection.test.ts +0 -377
  11. package/core/__tests__/agentic/cache-eviction.test.ts +0 -294
  12. package/core/__tests__/agentic/command-context.test.ts +0 -281
  13. package/core/__tests__/agentic/command-executor.test.ts +0 -659
  14. package/core/__tests__/agentic/domain-classifier.test.ts +0 -330
  15. package/core/__tests__/agentic/injection-validator.test.ts +0 -255
  16. package/core/__tests__/agentic/memory-system.test.ts +0 -281
  17. package/core/__tests__/agentic/plan-mode.test.ts +0 -386
  18. package/core/__tests__/agentic/prompt-assembly.test.ts +0 -298
  19. package/core/__tests__/agentic/prompt-builder.test.ts +0 -243
  20. package/core/__tests__/agentic/response-validator.test.ts +0 -263
  21. package/core/__tests__/agentic/semantic-matching.test.ts +0 -131
  22. package/core/__tests__/agentic/smart-context.test.ts +0 -372
  23. package/core/__tests__/agentic/tech-normalizer.test.ts +0 -136
  24. package/core/__tests__/agentic/token-budget.test.ts +0 -294
  25. package/core/__tests__/ai-tools/formatters.test.ts +0 -476
  26. package/core/__tests__/domain/bm25.test.ts +0 -225
  27. package/core/__tests__/domain/change-propagator.test.ts +0 -100
  28. package/core/__tests__/domain/fibonacci.test.ts +0 -113
  29. package/core/__tests__/domain/file-hasher.test.ts +0 -146
  30. package/core/__tests__/domain/file-ranker.test.ts +0 -169
  31. package/core/__tests__/domain/git-cochange.test.ts +0 -121
  32. package/core/__tests__/domain/import-graph.test.ts +0 -156
  33. package/core/__tests__/domain/velocity.test.ts +0 -623
  34. package/core/__tests__/infrastructure/performance-tracker.test.ts +0 -328
  35. package/core/__tests__/schemas/model.test.ts +0 -272
  36. package/core/__tests__/services/dependency-validator.test.ts +0 -175
  37. package/core/__tests__/services/hierarchical-agent-resolver.test.ts +0 -359
  38. package/core/__tests__/services/nested-context-resolver.test.ts +0 -443
  39. package/core/__tests__/services/project-index.test.ts +0 -355
  40. package/core/__tests__/services/staleness-checker.test.ts +0 -204
  41. package/core/__tests__/storage/analysis-storage.test.ts +0 -641
  42. package/core/__tests__/storage/archive-storage.test.ts +0 -455
  43. package/core/__tests__/storage/safe-reader.test.ts +0 -262
  44. package/core/__tests__/storage/sqlite-migration.test.ts +0 -1016
  45. package/core/__tests__/storage/state-storage-feedback.test.ts +0 -463
  46. package/core/__tests__/storage/state-storage-history.test.ts +0 -469
  47. package/core/__tests__/storage/storage-manager.test.ts +0 -383
  48. package/core/__tests__/storage/subtask-handoff.test.ts +0 -237
  49. package/core/__tests__/types/fs.test.ts +0 -125
  50. package/core/__tests__/utils/date-helper.test.ts +0 -449
  51. package/core/__tests__/utils/output.test.ts +0 -278
  52. package/core/__tests__/utils/preserve-sections.test.ts +0 -216
  53. package/core/__tests__/utils/project-commands.test.ts +0 -71
  54. package/core/__tests__/utils/retry.test.ts +0 -381
  55. package/core/__tests__/workflow/state-machine.test.ts +0 -216
  56. package/core/agentic/agent-router.ts +0 -150
  57. package/core/agentic/anti-hallucination.ts +0 -141
  58. package/core/agentic/chain-of-thought.ts +0 -234
  59. package/core/agentic/command-classifier.ts +0 -141
  60. package/core/agentic/command-context.ts +0 -168
  61. package/core/agentic/command-executor.ts +0 -471
  62. package/core/agentic/context-builder.ts +0 -285
  63. package/core/agentic/domain-classifier.ts +0 -525
  64. package/core/agentic/environment-block.ts +0 -102
  65. package/core/agentic/ground-truth.ts +0 -706
  66. package/core/agentic/index.ts +0 -193
  67. package/core/agentic/injection-validator.ts +0 -208
  68. package/core/agentic/loop-detector.ts +0 -451
  69. package/core/agentic/memory-system.ts +0 -1547
  70. package/core/agentic/orchestrator-executor.ts +0 -579
  71. package/core/agentic/plan-mode.ts +0 -525
  72. package/core/agentic/prompt-builder.ts +0 -1069
  73. package/core/agentic/response-validator.ts +0 -98
  74. package/core/agentic/services.ts +0 -167
  75. package/core/agentic/skill-loader.ts +0 -106
  76. package/core/agentic/smart-context.ts +0 -393
  77. package/core/agentic/tech-normalizer.ts +0 -167
  78. package/core/agentic/template-executor.ts +0 -272
  79. package/core/agentic/template-loader.ts +0 -109
  80. package/core/agentic/token-budget.ts +0 -226
  81. package/core/agentic/tool-registry.ts +0 -146
  82. package/core/agents/index.ts +0 -28
  83. package/core/agents/performance.ts +0 -429
  84. package/core/ai-tools/formatters.ts +0 -341
  85. package/core/ai-tools/generator.ts +0 -144
  86. package/core/ai-tools/index.ts +0 -15
  87. package/core/ai-tools/registry.ts +0 -201
  88. package/core/bus/bus.ts +0 -314
  89. package/core/bus/index.ts +0 -8
  90. package/core/cli/linear.ts +0 -500
  91. package/core/cli/lint-meta-commentary.ts +0 -177
  92. package/core/cli/start.ts +0 -386
  93. package/core/commands/analysis.ts +0 -1274
  94. package/core/commands/analytics.ts +0 -342
  95. package/core/commands/base.ts +0 -118
  96. package/core/commands/cleanup.ts +0 -157
  97. package/core/commands/command-data.ts +0 -463
  98. package/core/commands/commands.ts +0 -306
  99. package/core/commands/context.ts +0 -238
  100. package/core/commands/design.ts +0 -77
  101. package/core/commands/index.ts +0 -19
  102. package/core/commands/maintenance.ts +0 -77
  103. package/core/commands/performance.ts +0 -114
  104. package/core/commands/planning.ts +0 -662
  105. package/core/commands/register.ts +0 -127
  106. package/core/commands/registry.ts +0 -444
  107. package/core/commands/setup.ts +0 -280
  108. package/core/commands/shipping.ts +0 -267
  109. package/core/commands/snapshots.ts +0 -297
  110. package/core/commands/uninstall.ts +0 -542
  111. package/core/commands/velocity.ts +0 -149
  112. package/core/commands/workflow.ts +0 -505
  113. package/core/config/command-context.config.json +0 -66
  114. package/core/constants/index.ts +0 -379
  115. package/core/context/generator.ts +0 -368
  116. package/core/context-tools/files-tool.ts +0 -577
  117. package/core/context-tools/imports-tool.ts +0 -400
  118. package/core/context-tools/index.ts +0 -434
  119. package/core/context-tools/recent-tool.ts +0 -301
  120. package/core/context-tools/signatures-tool.ts +0 -495
  121. package/core/context-tools/summary-tool.ts +0 -301
  122. package/core/context-tools/token-counter.ts +0 -273
  123. package/core/context-tools/types.ts +0 -253
  124. package/core/domain/agent-generator.ts +0 -186
  125. package/core/domain/agent-loader.ts +0 -419
  126. package/core/domain/analyzer.ts +0 -387
  127. package/core/domain/architecture-generator.ts +0 -108
  128. package/core/domain/bm25.ts +0 -525
  129. package/core/domain/change-propagator.ts +0 -162
  130. package/core/domain/context-estimator.ts +0 -175
  131. package/core/domain/fibonacci.ts +0 -128
  132. package/core/domain/file-hasher.ts +0 -296
  133. package/core/domain/file-ranker.ts +0 -151
  134. package/core/domain/git-cochange.ts +0 -250
  135. package/core/domain/import-graph.ts +0 -315
  136. package/core/domain/snapshot-manager.ts +0 -415
  137. package/core/domain/task-stack.ts +0 -578
  138. package/core/domain/velocity.ts +0 -470
  139. package/core/errors.ts +0 -335
  140. package/core/events/events.ts +0 -85
  141. package/core/events/index.ts +0 -8
  142. package/core/index.ts +0 -481
  143. package/core/infrastructure/agent-detector.ts +0 -135
  144. package/core/infrastructure/ai-provider.ts +0 -578
  145. package/core/infrastructure/author-detector.ts +0 -133
  146. package/core/infrastructure/capability-installer.ts +0 -76
  147. package/core/infrastructure/claude-agent.ts +0 -297
  148. package/core/infrastructure/command-installer.ts +0 -752
  149. package/core/infrastructure/config-manager.ts +0 -364
  150. package/core/infrastructure/editors-config.ts +0 -172
  151. package/core/infrastructure/path-manager.ts +0 -571
  152. package/core/infrastructure/performance-tracker.ts +0 -326
  153. package/core/infrastructure/permission-manager.ts +0 -289
  154. package/core/infrastructure/setup.ts +0 -1061
  155. package/core/infrastructure/update-checker.ts +0 -246
  156. package/core/integrations/issue-tracker/enricher.ts +0 -271
  157. package/core/integrations/issue-tracker/index.ts +0 -8
  158. package/core/integrations/issue-tracker/manager.ts +0 -286
  159. package/core/integrations/issue-tracker/types.ts +0 -310
  160. package/core/integrations/jira/cache.ts +0 -57
  161. package/core/integrations/jira/client.ts +0 -688
  162. package/core/integrations/jira/index.ts +0 -23
  163. package/core/integrations/jira/service.ts +0 -244
  164. package/core/integrations/linear/cache.ts +0 -68
  165. package/core/integrations/linear/client.ts +0 -436
  166. package/core/integrations/linear/index.ts +0 -20
  167. package/core/integrations/linear/service.ts +0 -260
  168. package/core/integrations/linear/sync.ts +0 -314
  169. package/core/outcomes/analyzer.ts +0 -286
  170. package/core/outcomes/index.ts +0 -34
  171. package/core/outcomes/recorder.ts +0 -195
  172. package/core/plugin/builtin/webhook.ts +0 -148
  173. package/core/plugin/hooks.ts +0 -315
  174. package/core/plugin/index.ts +0 -50
  175. package/core/plugin/loader.ts +0 -354
  176. package/core/plugin/registry.ts +0 -326
  177. package/core/schemas/agents.ts +0 -27
  178. package/core/schemas/analysis.ts +0 -530
  179. package/core/schemas/classification.ts +0 -91
  180. package/core/schemas/command-context.ts +0 -29
  181. package/core/schemas/enriched-task.ts +0 -291
  182. package/core/schemas/ideas.ts +0 -114
  183. package/core/schemas/index.ts +0 -53
  184. package/core/schemas/issues.ts +0 -159
  185. package/core/schemas/llm-output.ts +0 -170
  186. package/core/schemas/metrics.ts +0 -143
  187. package/core/schemas/model.ts +0 -153
  188. package/core/schemas/outcomes.ts +0 -487
  189. package/core/schemas/performance.ts +0 -128
  190. package/core/schemas/permissions.ts +0 -180
  191. package/core/schemas/prd.ts +0 -450
  192. package/core/schemas/project.ts +0 -57
  193. package/core/schemas/roadmap.ts +0 -322
  194. package/core/schemas/schemas.ts +0 -38
  195. package/core/schemas/shipped.ts +0 -109
  196. package/core/schemas/state.ts +0 -284
  197. package/core/schemas/velocity.ts +0 -103
  198. package/core/server/index.ts +0 -21
  199. package/core/server/routes-extended.ts +0 -566
  200. package/core/server/routes.ts +0 -176
  201. package/core/server/server.ts +0 -149
  202. package/core/server/sse.ts +0 -192
  203. package/core/services/agent-generator.ts +0 -385
  204. package/core/services/agent-service.ts +0 -168
  205. package/core/services/breakdown-service.ts +0 -124
  206. package/core/services/context-generator.ts +0 -445
  207. package/core/services/context-selector.ts +0 -429
  208. package/core/services/dependency-validator.ts +0 -318
  209. package/core/services/diff-generator.ts +0 -313
  210. package/core/services/doctor-service.ts +0 -423
  211. package/core/services/file-categorizer.ts +0 -448
  212. package/core/services/file-scorer.ts +0 -270
  213. package/core/services/git-analyzer.ts +0 -293
  214. package/core/services/hierarchical-agent-resolver.ts +0 -236
  215. package/core/services/hooks-service.ts +0 -685
  216. package/core/services/index.ts +0 -46
  217. package/core/services/local-state-generator.ts +0 -158
  218. package/core/services/memory-service.ts +0 -181
  219. package/core/services/nested-context-resolver.ts +0 -842
  220. package/core/services/project-index.ts +0 -911
  221. package/core/services/project-service.ts +0 -155
  222. package/core/services/session-tracker.ts +0 -287
  223. package/core/services/skill-installer.ts +0 -447
  224. package/core/services/skill-lock.ts +0 -132
  225. package/core/services/skill-service.ts +0 -306
  226. package/core/services/stack-detector.ts +0 -229
  227. package/core/services/staleness-checker.ts +0 -327
  228. package/core/services/sync-service.ts +0 -1515
  229. package/core/services/sync-verifier.ts +0 -253
  230. package/core/services/watch-service.ts +0 -312
  231. package/core/session/compaction.ts +0 -248
  232. package/core/session/index.ts +0 -35
  233. package/core/session/log-migration.ts +0 -88
  234. package/core/session/metrics.ts +0 -323
  235. package/core/session/session-log-manager.ts +0 -307
  236. package/core/session/task-session-manager.ts +0 -404
  237. package/core/session/utils.ts +0 -51
  238. package/core/storage/analysis-storage.ts +0 -373
  239. package/core/storage/archive-storage.ts +0 -205
  240. package/core/storage/database.ts +0 -575
  241. package/core/storage/ideas-storage.ts +0 -298
  242. package/core/storage/index-storage.ts +0 -523
  243. package/core/storage/index.ts +0 -79
  244. package/core/storage/metrics-storage.ts +0 -321
  245. package/core/storage/migrate-json.ts +0 -720
  246. package/core/storage/queue-storage.ts +0 -336
  247. package/core/storage/safe-reader.ts +0 -105
  248. package/core/storage/shipped-storage.ts +0 -253
  249. package/core/storage/state-storage.ts +0 -1035
  250. package/core/storage/storage-manager.ts +0 -205
  251. package/core/storage/storage.ts +0 -177
  252. package/core/storage/velocity-storage.ts +0 -149
  253. package/core/sync/auth-config.ts +0 -138
  254. package/core/sync/index.ts +0 -31
  255. package/core/sync/oauth-handler.ts +0 -143
  256. package/core/sync/sync-client.ts +0 -251
  257. package/core/sync/sync-manager.ts +0 -327
  258. package/core/tsconfig.json +0 -22
  259. package/core/types/agentic.ts +0 -760
  260. package/core/types/agents.ts +0 -150
  261. package/core/types/bus.ts +0 -193
  262. package/core/types/citations.ts +0 -22
  263. package/core/types/commands.ts +0 -399
  264. package/core/types/config.ts +0 -92
  265. package/core/types/core.ts +0 -96
  266. package/core/types/diff.ts +0 -41
  267. package/core/types/domain.ts +0 -71
  268. package/core/types/errors.ts +0 -111
  269. package/core/types/events.ts +0 -42
  270. package/core/types/fs.ts +0 -72
  271. package/core/types/index.ts +0 -510
  272. package/core/types/infrastructure.ts +0 -210
  273. package/core/types/integrations.ts +0 -31
  274. package/core/types/jira.ts +0 -51
  275. package/core/types/logger.ts +0 -17
  276. package/core/types/memory.ts +0 -313
  277. package/core/types/outcomes.ts +0 -190
  278. package/core/types/output.ts +0 -47
  279. package/core/types/plugin.ts +0 -25
  280. package/core/types/project-sync.ts +0 -129
  281. package/core/types/provider.ts +0 -163
  282. package/core/types/server.ts +0 -71
  283. package/core/types/services.ts +0 -84
  284. package/core/types/session.ts +0 -135
  285. package/core/types/stack.ts +0 -19
  286. package/core/types/storage.ts +0 -318
  287. package/core/types/sync-verifier.ts +0 -33
  288. package/core/types/sync.ts +0 -121
  289. package/core/types/task.ts +0 -72
  290. package/core/types/template.ts +0 -24
  291. package/core/types/utils.ts +0 -92
  292. package/core/types/workflow.ts +0 -23
  293. package/core/utils/agent-stream.ts +0 -140
  294. package/core/utils/animations.ts +0 -251
  295. package/core/utils/branding.ts +0 -88
  296. package/core/utils/cache.ts +0 -187
  297. package/core/utils/citations.ts +0 -39
  298. package/core/utils/collection-filters.ts +0 -209
  299. package/core/utils/date-helper.ts +0 -176
  300. package/core/utils/error-messages.ts +0 -38
  301. package/core/utils/file-helper.ts +0 -277
  302. package/core/utils/fs-helpers.ts +0 -14
  303. package/core/utils/help.ts +0 -314
  304. package/core/utils/jsonl-helper.ts +0 -290
  305. package/core/utils/keychain.ts +0 -127
  306. package/core/utils/logger.ts +0 -77
  307. package/core/utils/markdown-builder.ts +0 -280
  308. package/core/utils/next-steps.ts +0 -95
  309. package/core/utils/output.ts +0 -403
  310. package/core/utils/preserve-sections.ts +0 -218
  311. package/core/utils/project-commands.ts +0 -126
  312. package/core/utils/project-credentials.ts +0 -143
  313. package/core/utils/provider-cache.ts +0 -49
  314. package/core/utils/retry.ts +0 -318
  315. package/core/utils/runtime.ts +0 -108
  316. package/core/utils/session-helper.ts +0 -278
  317. package/core/utils/subtask-table.ts +0 -227
  318. package/core/utils/version.ts +0 -128
  319. package/core/wizard/index.ts +0 -13
  320. package/core/wizard/onboarding.ts +0 -633
  321. package/core/workflow/index.ts +0 -7
  322. package/core/workflow/state-machine.ts +0 -198
  323. package/core/workflow/workflow-preferences.ts +0 -294
  324. package/dist/core/infrastructure/command-installer.js +0 -1141
  325. package/dist/core/infrastructure/editors-config.js +0 -177
  326. package/dist/core/infrastructure/setup.js +0 -2244
  327. package/dist/core/utils/version.js +0 -141
  328. package/templates/agentic/agent-routing.md +0 -45
  329. package/templates/agentic/agents/uxui.md +0 -63
  330. package/templates/agentic/checklist-routing.md +0 -98
  331. package/templates/agentic/orchestrator.md +0 -68
  332. package/templates/agentic/task-fragmentation.md +0 -89
  333. package/templates/agents/AGENTS.md +0 -68
  334. package/templates/analysis/analyze.md +0 -84
  335. package/templates/analysis/patterns.md +0 -60
  336. package/templates/antigravity/SKILL.md +0 -39
  337. package/templates/architect/discovery.md +0 -67
  338. package/templates/architect/phases.md +0 -59
  339. package/templates/checklists/architecture.md +0 -28
  340. package/templates/checklists/code-quality.md +0 -28
  341. package/templates/checklists/data.md +0 -33
  342. package/templates/checklists/documentation.md +0 -33
  343. package/templates/checklists/infrastructure.md +0 -33
  344. package/templates/checklists/performance.md +0 -33
  345. package/templates/checklists/security.md +0 -33
  346. package/templates/checklists/testing.md +0 -33
  347. package/templates/checklists/ux-ui.md +0 -37
  348. package/templates/commands/analyze.md +0 -56
  349. package/templates/commands/auth.md +0 -234
  350. package/templates/commands/bug.md +0 -163
  351. package/templates/commands/cleanup.md +0 -19
  352. package/templates/commands/dash.md +0 -99
  353. package/templates/commands/design.md +0 -15
  354. package/templates/commands/done.md +0 -291
  355. package/templates/commands/enrich.md +0 -174
  356. package/templates/commands/git.md +0 -295
  357. package/templates/commands/history.md +0 -389
  358. package/templates/commands/idea.md +0 -88
  359. package/templates/commands/impact.md +0 -864
  360. package/templates/commands/init.md +0 -54
  361. package/templates/commands/jira.md +0 -278
  362. package/templates/commands/linear.md +0 -288
  363. package/templates/commands/merge.md +0 -206
  364. package/templates/commands/next.md +0 -80
  365. package/templates/commands/p.md +0 -67
  366. package/templates/commands/p.toml +0 -37
  367. package/templates/commands/pause.md +0 -136
  368. package/templates/commands/plan.md +0 -696
  369. package/templates/commands/prd.md +0 -356
  370. package/templates/commands/resume.md +0 -171
  371. package/templates/commands/review.md +0 -276
  372. package/templates/commands/serve.md +0 -118
  373. package/templates/commands/setup.md +0 -91
  374. package/templates/commands/ship.md +0 -475
  375. package/templates/commands/skill.md +0 -259
  376. package/templates/commands/spec.md +0 -218
  377. package/templates/commands/status.md +0 -207
  378. package/templates/commands/sync.md +0 -104
  379. package/templates/commands/task.md +0 -312
  380. package/templates/commands/test.md +0 -93
  381. package/templates/commands/update.md +0 -63
  382. package/templates/commands/verify.md +0 -204
  383. package/templates/commands/workflow.md +0 -150
  384. package/templates/config/skill-mappings.json +0 -82
  385. package/templates/context/dashboard.md +0 -256
  386. package/templates/context/roadmap.md +0 -221
  387. package/templates/cursor/commands/bug.md +0 -8
  388. package/templates/cursor/commands/done.md +0 -4
  389. package/templates/cursor/commands/pause.md +0 -6
  390. package/templates/cursor/commands/resume.md +0 -4
  391. package/templates/cursor/commands/ship.md +0 -8
  392. package/templates/cursor/commands/sync.md +0 -4
  393. package/templates/cursor/commands/task.md +0 -8
  394. package/templates/cursor/p.md +0 -29
  395. package/templates/cursor/router.mdc +0 -28
  396. package/templates/design/api.md +0 -95
  397. package/templates/design/architecture.md +0 -77
  398. package/templates/design/component.md +0 -89
  399. package/templates/design/database.md +0 -78
  400. package/templates/design/flow.md +0 -94
  401. package/templates/global/ANTIGRAVITY.md +0 -254
  402. package/templates/global/CLAUDE.md +0 -497
  403. package/templates/global/CURSOR.mdc +0 -266
  404. package/templates/global/GEMINI.md +0 -293
  405. package/templates/global/STORAGE-SPEC.md +0 -391
  406. package/templates/global/WINDSURF.md +0 -266
  407. package/templates/global/modules/CLAUDE-commands.md +0 -70
  408. package/templates/global/modules/CLAUDE-core.md +0 -105
  409. package/templates/global/modules/CLAUDE-git.md +0 -50
  410. package/templates/global/modules/CLAUDE-intelligence.md +0 -92
  411. package/templates/global/modules/CLAUDE-storage.md +0 -50
  412. package/templates/global/modules/module-config.json +0 -36
  413. package/templates/mcp-config.json +0 -19
  414. package/templates/permissions/default.jsonc +0 -60
  415. package/templates/permissions/permissive.jsonc +0 -49
  416. package/templates/permissions/strict.jsonc +0 -58
  417. package/templates/planning-methodology.md +0 -195
  418. package/templates/skills/code-review.md +0 -47
  419. package/templates/skills/debug.md +0 -61
  420. package/templates/skills/refactor.md +0 -47
  421. package/templates/subagents/agent-base.md +0 -20
  422. package/templates/subagents/domain/backend.md +0 -109
  423. package/templates/subagents/domain/database.md +0 -121
  424. package/templates/subagents/domain/devops.md +0 -152
  425. package/templates/subagents/domain/frontend.md +0 -103
  426. package/templates/subagents/domain/testing.md +0 -169
  427. package/templates/subagents/pm-expert.md +0 -366
  428. package/templates/subagents/workflow/chief-architect.md +0 -657
  429. package/templates/subagents/workflow/prjct-planner.md +0 -159
  430. package/templates/subagents/workflow/prjct-shipper.md +0 -188
  431. package/templates/subagents/workflow/prjct-workflow.md +0 -98
  432. package/templates/tools/bash.txt +0 -22
  433. package/templates/tools/edit.txt +0 -18
  434. package/templates/tools/glob.txt +0 -19
  435. package/templates/tools/grep.txt +0 -21
  436. package/templates/tools/read.txt +0 -14
  437. package/templates/tools/task.txt +0 -20
  438. package/templates/tools/webfetch.txt +0 -16
  439. package/templates/tools/websearch.txt +0 -18
  440. package/templates/tools/write.txt +0 -17
  441. package/templates/windsurf/router.md +0 -28
  442. package/templates/windsurf/workflows/bug.md +0 -8
  443. package/templates/windsurf/workflows/done.md +0 -4
  444. package/templates/windsurf/workflows/pause.md +0 -4
  445. package/templates/windsurf/workflows/resume.md +0 -4
  446. package/templates/windsurf/workflows/ship.md +0 -8
  447. package/templates/windsurf/workflows/sync.md +0 -4
  448. package/templates/windsurf/workflows/task.md +0 -8
@@ -1,1515 +0,0 @@
1
- /**
2
- * SyncService - Comprehensive Project Sync
3
- *
4
- * Handles ALL sync operations in a single TypeScript execution:
5
- * - Git analysis
6
- * - Project stats
7
- * - Context file generation
8
- * - Agent generation
9
- * - Skill configuration
10
- * - State updates
11
- *
12
- * This eliminates the need for Claude to make 50+ individual tool calls.
13
- * Instead, one command does everything.
14
- *
15
- * @version 1.0.0
16
- */
17
-
18
- import { exec } from 'node:child_process'
19
- import fs from 'node:fs/promises'
20
- import os from 'node:os'
21
- import path from 'node:path'
22
- import { promisify } from 'node:util'
23
- import {
24
- DEFAULT_AI_TOOLS,
25
- detectInstalledTools,
26
- generateAIToolContexts,
27
- type ProjectContext,
28
- resolveToolIds,
29
- } from '../ai-tools'
30
- import { indexProject } from '../domain/bm25'
31
- import { affectedDomains, propagateChanges } from '../domain/change-propagator'
32
- import { detectChanges, hasHashRegistry, saveHashes } from '../domain/file-hasher'
33
- import { indexCoChanges } from '../domain/git-cochange'
34
- import { indexImports } from '../domain/import-graph'
35
- import { getErrorMessage } from '../errors'
36
- import commandInstaller from '../infrastructure/command-installer'
37
- import configManager from '../infrastructure/config-manager'
38
- import pathManager from '../infrastructure/path-manager'
39
- import { analysisStorage } from '../storage/analysis-storage'
40
- import { archiveStorage } from '../storage/archive-storage'
41
- import { ideasStorage } from '../storage/ideas-storage'
42
- import { metricsStorage } from '../storage/metrics-storage'
43
- import { migrateJsonToSqlite } from '../storage/migrate-json'
44
- import { queueStorage } from '../storage/queue-storage'
45
- import { shippedStorage } from '../storage/shipped-storage'
46
- import { stateStorage } from '../storage/state-storage'
47
- import type {
48
- GitData,
49
- IncrementalInfo,
50
- ProjectCommands,
51
- ProjectStats,
52
- ProjectSyncResult,
53
- StackDetection,
54
- SyncAgentInfo,
55
- SyncMetrics,
56
- SyncOptions,
57
- VerificationReport,
58
- } from '../types'
59
- import { type ContextSources, defaultSources, type SourceInfo } from '../utils/citations'
60
- import * as dateHelper from '../utils/date-helper'
61
- import log from '../utils/logger'
62
- import { ContextFileGenerator } from './context-generator'
63
- import { localStateGenerator } from './local-state-generator'
64
- import { memoryService } from './memory-service'
65
- import { skillInstaller } from './skill-installer'
66
- import { StackDetector } from './stack-detector'
67
- import { syncVerifier } from './sync-verifier'
68
-
69
- const execAsync = promisify(exec)
70
-
71
- // ============================================================================
72
- // SYNC SERVICE
73
- // ============================================================================
74
-
75
- class SyncService {
76
- private projectPath: string
77
- private projectId: string | null = null
78
- private globalPath: string = ''
79
- private cliVersion: string = '0.0.0'
80
- /** Task feedback context for agent generation (PRJ-272) */
81
- private taskFeedbackContext?: {
82
- patternsDiscovered: string[]
83
- knownGotchas: string[]
84
- agentAccuracy: Array<{ agent: string; rating: string; note?: string }>
85
- }
86
-
87
- constructor() {
88
- this.projectPath = process.cwd()
89
- }
90
-
91
- /**
92
- * Main sync method - does everything in one call
93
- */
94
- async sync(
95
- projectPath: string = process.cwd(),
96
- options: SyncOptions = {}
97
- ): Promise<ProjectSyncResult> {
98
- this.projectPath = projectPath
99
- const startTime = Date.now()
100
-
101
- // Resolve AI tools: supports 'auto', 'all', or specific list
102
- // Default behavior: claude + any detected IDE tools (.cursor/, .windsurf/)
103
- let aiToolIds: string[]
104
- if (!options.aiTools || options.aiTools.length === 0) {
105
- // Start with default CLI tools and add detected IDE tools
106
- const detectedIdeTools = (await detectInstalledTools(projectPath)).filter(
107
- (id) => !DEFAULT_AI_TOOLS.includes(id)
108
- )
109
- aiToolIds = [...DEFAULT_AI_TOOLS, ...detectedIdeTools]
110
- } else if (options.aiTools[0] === 'auto') {
111
- aiToolIds = await detectInstalledTools(projectPath)
112
- if (aiToolIds.length === 0) aiToolIds = ['claude'] // fallback
113
- } else if (options.aiTools[0] === 'all') {
114
- aiToolIds = await resolveToolIds('all', projectPath)
115
- } else {
116
- aiToolIds = options.aiTools
117
- }
118
-
119
- try {
120
- // 1. Get project config
121
- this.projectId = await configManager.getProjectId(projectPath)
122
- if (!this.projectId) {
123
- return {
124
- success: false,
125
- projectId: '',
126
- cliVersion: '',
127
- git: this.emptyGitData(),
128
- stats: this.emptyStats(),
129
- commands: this.emptyCommands(),
130
- stack: this.emptyStack(),
131
- agents: [],
132
- skills: [],
133
- skillsInstalled: [],
134
- contextFiles: [],
135
- aiTools: [],
136
- error: 'No prjct project. Run p. init first.',
137
- }
138
- }
139
-
140
- this.globalPath = pathManager.getGlobalProjectPath(this.projectId)
141
- this.cliVersion = await this.getCliVersion()
142
-
143
- // 2. Ensure directories exist (non-blocking)
144
- const ensureDirsPromise = this.ensureDirectories()
145
-
146
- // 2b. Auto-migrate JSON → SQLite (idempotent, skips if already done)
147
- await ensureDirsPromise
148
- await migrateJsonToSqlite(this.projectId)
149
-
150
- // 3. Gather all data IN PARALLEL (30-50% speedup)
151
- // These operations are independent and can run concurrently
152
- const [git, stats, commands, stack] = await Promise.all([
153
- this.analyzeGit(),
154
- this.gatherStats(),
155
- this.detectCommands(),
156
- this.detectStack(),
157
- ])
158
-
159
- // 3a. Incremental change detection
160
- // Determine if we can skip expensive operations based on file changes
161
- const isFullSync = options.full === true
162
- let incrementalInfo: IncrementalInfo | undefined
163
- let shouldRebuildIndexes = true
164
- let shouldRegenerateAgents = true
165
- let changedDomains = new Set<string>()
166
-
167
- if (!isFullSync && hasHashRegistry(this.projectId!)) {
168
- try {
169
- const { diff, currentHashes } = await detectChanges(this.projectPath, this.projectId!)
170
- const totalChanged = diff.added.length + diff.modified.length + diff.deleted.length
171
-
172
- if (totalChanged === 0 && !options.changedFiles?.length) {
173
- // Nothing changed — skip expensive rebuilds
174
- shouldRebuildIndexes = false
175
- shouldRegenerateAgents = false
176
- incrementalInfo = {
177
- isIncremental: true,
178
- filesChanged: 0,
179
- filesUnchanged: diff.unchanged.length,
180
- indexesRebuilt: false,
181
- agentsRegenerated: false,
182
- affectedDomains: [],
183
- }
184
- } else {
185
- // Some files changed — propagate through import graph
186
- const propagated = propagateChanges(diff, this.projectId!)
187
- changedDomains = affectedDomains(propagated.allAffected)
188
-
189
- // Only rebuild indexes if source files changed
190
- const sourceExtensions = new Set(['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'])
191
- const hasSourceChanges = propagated.allAffected.some((f) => {
192
- const ext = f.substring(f.lastIndexOf('.'))
193
- return sourceExtensions.has(ext)
194
- })
195
- shouldRebuildIndexes = hasSourceChanges
196
-
197
- // Only regenerate agents if stack/domains might have changed
198
- // (new files added to previously empty domains, or config files changed)
199
- const configChanged = propagated.directlyChanged.some(
200
- (f) =>
201
- f === 'package.json' ||
202
- f === 'tsconfig.json' ||
203
- f.includes('Dockerfile') ||
204
- f.includes('docker-compose')
205
- )
206
- shouldRegenerateAgents = configChanged
207
-
208
- incrementalInfo = {
209
- isIncremental: true,
210
- filesChanged: totalChanged,
211
- filesUnchanged: diff.unchanged.length,
212
- indexesRebuilt: shouldRebuildIndexes,
213
- agentsRegenerated: shouldRegenerateAgents,
214
- affectedDomains: Array.from(changedDomains),
215
- }
216
- }
217
-
218
- // Save updated hashes AFTER determining diff (commit new state)
219
- saveHashes(this.projectId!, currentHashes)
220
- } catch (error) {
221
- log.debug('Incremental detection failed, falling back to full sync', {
222
- error: getErrorMessage(error),
223
- })
224
- // Fall through to full sync
225
- }
226
- } else {
227
- // First sync or --full flag: compute and save hashes for next time
228
- try {
229
- const { currentHashes } = await detectChanges(this.projectPath, this.projectId!)
230
- saveHashes(this.projectId!, currentHashes)
231
- } catch (error) {
232
- log.debug('Hash computation failed (non-critical)', {
233
- error: getErrorMessage(error),
234
- })
235
- }
236
- }
237
-
238
- // 3b. Build file-ranking indexes IN PARALLEL (BM25, import graph, co-change)
239
- // Skip if no source files changed (incremental optimization)
240
- if (shouldRebuildIndexes) {
241
- try {
242
- await Promise.all([
243
- indexProject(this.projectPath, this.projectId!),
244
- indexImports(this.projectPath, this.projectId!),
245
- indexCoChanges(this.projectPath, this.projectId!),
246
- ])
247
- } catch (error) {
248
- log.debug('File ranking index build failed (non-critical)', {
249
- error: getErrorMessage(error),
250
- })
251
- }
252
- }
253
-
254
- // 4. Generate all files (depends on gathered data)
255
- // Load task feedback for agent generation (PRJ-272)
256
- let taskFeedbackContext:
257
- | {
258
- patternsDiscovered: string[]
259
- knownGotchas: string[]
260
- agentAccuracy: Array<{ agent: string; rating: string; note?: string }>
261
- }
262
- | undefined
263
- if (shouldRegenerateAgents) {
264
- try {
265
- const feedback = await stateStorage.getAggregatedFeedback(this.projectId!)
266
- if (
267
- feedback.patternsDiscovered.length > 0 ||
268
- feedback.knownGotchas.length > 0 ||
269
- feedback.agentAccuracy.length > 0
270
- ) {
271
- taskFeedbackContext = feedback
272
- }
273
- } catch {
274
- // Feedback loading failure should not block agent generation
275
- }
276
- }
277
-
278
- // Skip agent regeneration if nothing structural changed
279
- const agents = shouldRegenerateAgents
280
- ? await this.generateAgents(stack, stats, taskFeedbackContext)
281
- : await this.loadExistingAgents()
282
- const skills = this.configureSkills(agents)
283
- const skillsInstalled = shouldRegenerateAgents ? await this.autoInstallSkills(agents) : []
284
- const sources = this.buildSources(stats, commands)
285
- const contextFiles = await this.generateContextFiles(git, stats, commands, agents, sources)
286
-
287
- // 5. Generate AI tool context files (multi-agent output)
288
- const projectContext: ProjectContext = {
289
- projectId: this.projectId,
290
- name: stats.name,
291
- version: stats.version,
292
- ecosystem: stats.ecosystem,
293
- projectType: stats.projectType,
294
- languages: stats.languages,
295
- frameworks: stats.frameworks,
296
- repoPath: this.projectPath,
297
- branch: git.branch,
298
- fileCount: stats.fileCount,
299
- commits: git.commits,
300
- hasChanges: git.hasChanges,
301
- commands,
302
- agents: {
303
- workflow: agents.filter((a) => a.type === 'workflow').map((a) => a.name),
304
- domain: agents.filter((a) => a.type === 'domain').map((a) => a.name),
305
- },
306
- sources,
307
- }
308
-
309
- const aiToolResults = await generateAIToolContexts(
310
- projectContext,
311
- this.globalPath,
312
- this.projectPath,
313
- aiToolIds
314
- )
315
-
316
- // 6-8. Update files IN PARALLEL (write to different files)
317
- await Promise.all([
318
- this.updateProjectJson(git, stats),
319
- this.updateStateJson(stats, stack),
320
- this.logToMemory(git, stats),
321
- this.saveDraftAnalysis(git, stats, stack),
322
- ])
323
-
324
- // 9. Record metrics for value dashboard
325
- const duration = Date.now() - startTime
326
- const syncMetrics = await this.recordSyncMetrics(stats, contextFiles, agents, duration)
327
-
328
- // 9b. Archive stale data (PRJ-267)
329
- await this.archiveStaleData()
330
-
331
- // 10. Update global config and commands (CLI does EVERYTHING)
332
- // This ensures `prjct sync` from terminal updates global CLAUDE.md and commands
333
- await commandInstaller.installGlobalConfig()
334
- await commandInstaller.syncCommands()
335
-
336
- // 11. Run verification checks (built-in + custom from config)
337
- let verification: VerificationReport | undefined
338
- try {
339
- const localConfig = await configManager.readConfig(this.projectPath)
340
- verification = await syncVerifier.verify(
341
- this.projectPath,
342
- this.globalPath,
343
- localConfig?.verification
344
- )
345
- } catch (error) {
346
- log.debug('Verification failed (non-critical)', { error: getErrorMessage(error) })
347
- }
348
-
349
- return {
350
- success: true,
351
- projectId: this.projectId,
352
- cliVersion: this.cliVersion,
353
- git,
354
- stats,
355
- commands,
356
- stack,
357
- agents,
358
- skills,
359
- skillsInstalled,
360
- contextFiles,
361
- aiTools: aiToolResults.map((r) => ({
362
- toolId: r.toolId,
363
- outputFile: r.outputFile,
364
- success: r.success,
365
- })),
366
- syncMetrics,
367
- verification,
368
- incremental: incrementalInfo,
369
- }
370
- } catch (error) {
371
- return {
372
- success: false,
373
- projectId: this.projectId || '',
374
- cliVersion: this.cliVersion,
375
- git: this.emptyGitData(),
376
- stats: this.emptyStats(),
377
- commands: this.emptyCommands(),
378
- stack: this.emptyStack(),
379
- agents: [],
380
- skills: [],
381
- skillsInstalled: [],
382
- contextFiles: [],
383
- aiTools: [],
384
- error: getErrorMessage(error),
385
- }
386
- }
387
- }
388
-
389
- // ==========================================================================
390
- // DIRECTORY SETUP
391
- // ==========================================================================
392
-
393
- private async ensureDirectories(): Promise<void> {
394
- const dirs = ['storage', 'context', 'agents', 'memory', 'analysis', 'config', 'sync']
395
- // Create all directories IN PARALLEL
396
- await Promise.all(
397
- dirs.map((dir) => fs.mkdir(path.join(this.globalPath, dir), { recursive: true }))
398
- )
399
- }
400
-
401
- // ==========================================================================
402
- // GIT ANALYSIS
403
- // ==========================================================================
404
-
405
- private async analyzeGit(): Promise<GitData> {
406
- const data: GitData = {
407
- branch: 'main',
408
- commits: 0,
409
- contributors: 0,
410
- hasChanges: false,
411
- stagedFiles: [],
412
- modifiedFiles: [],
413
- untrackedFiles: [],
414
- recentCommits: [],
415
- weeklyCommits: 0,
416
- }
417
-
418
- try {
419
- // Branch
420
- const { stdout: branch } = await execAsync('git branch --show-current', {
421
- cwd: this.projectPath,
422
- })
423
- data.branch = branch.trim() || 'main'
424
-
425
- // Total commits
426
- const { stdout: commits } = await execAsync('git rev-list --count HEAD', {
427
- cwd: this.projectPath,
428
- })
429
- data.commits = parseInt(commits.trim(), 10) || 0
430
-
431
- // Contributors
432
- const { stdout: contributors } = await execAsync('git shortlog -sn --all | wc -l', {
433
- cwd: this.projectPath,
434
- })
435
- data.contributors = parseInt(contributors.trim(), 10) || 0
436
-
437
- // Status
438
- const { stdout: status } = await execAsync('git status --porcelain', {
439
- cwd: this.projectPath,
440
- })
441
- const lines = status.trim().split('\n').filter(Boolean)
442
- data.hasChanges = lines.length > 0
443
-
444
- for (const line of lines) {
445
- const code = line.substring(0, 2)
446
- const file = line.substring(3)
447
- if (code.startsWith('A') || code.startsWith('M ')) {
448
- data.stagedFiles.push(file)
449
- } else if (code.includes('M')) {
450
- data.modifiedFiles.push(file)
451
- } else if (code.startsWith('??')) {
452
- data.untrackedFiles.push(file)
453
- }
454
- }
455
-
456
- // Recent commits
457
- const { stdout: log } = await execAsync(
458
- 'git log --oneline -20 --pretty=format:"%h|%s|%ad" --date=short',
459
- { cwd: this.projectPath }
460
- )
461
- data.recentCommits = log
462
- .split('\n')
463
- .filter(Boolean)
464
- .map((line) => {
465
- const [hash, message, date] = line.split('|')
466
- return { hash, message, date }
467
- })
468
-
469
- // Weekly commits
470
- const { stdout: weekly } = await execAsync('git log --oneline --since="1 week ago" | wc -l', {
471
- cwd: this.projectPath,
472
- })
473
- data.weeklyCommits = parseInt(weekly.trim(), 10) || 0
474
- } catch (error) {
475
- log.debug('Git analysis failed (not a git repo?)', { error: getErrorMessage(error) })
476
- }
477
-
478
- return data
479
- }
480
-
481
- // ==========================================================================
482
- // PROJECT STATS
483
- // ==========================================================================
484
-
485
- private async gatherStats(): Promise<ProjectStats> {
486
- const stats: ProjectStats = {
487
- fileCount: 0,
488
- version: '0.0.0',
489
- name: path.basename(this.projectPath),
490
- ecosystem: 'unknown',
491
- projectType: 'simple',
492
- languages: [],
493
- frameworks: [],
494
- }
495
-
496
- // Count files
497
- try {
498
- const { stdout } = await execAsync(
499
- 'find . -type f \\( -name "*.js" -o -name "*.ts" -o -name "*.tsx" -o -name "*.py" -o -name "*.go" -o -name "*.rs" \\) -not -path "./node_modules/*" -not -path "./.git/*" | wc -l',
500
- { cwd: this.projectPath }
501
- )
502
- stats.fileCount = parseInt(stdout.trim(), 10) || 0
503
- } catch (error) {
504
- log.debug('File count failed', { path: this.projectPath, error: getErrorMessage(error) })
505
- stats.fileCount = 0
506
- }
507
-
508
- // Read package.json
509
- try {
510
- const pkgPath = path.join(this.projectPath, 'package.json')
511
- const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf-8'))
512
- stats.version = pkg.version || '0.0.0'
513
- stats.name = pkg.name || stats.name
514
- stats.ecosystem = 'JavaScript'
515
-
516
- const deps = { ...pkg.dependencies, ...pkg.devDependencies }
517
-
518
- // Detect frameworks
519
- if (deps.react || deps['react-dom']) stats.frameworks.push('React')
520
- if (deps.next) stats.frameworks.push('Next.js')
521
- if (deps.vue) stats.frameworks.push('Vue')
522
- if (deps.express) stats.frameworks.push('Express')
523
- if (deps.hono) stats.frameworks.push('Hono')
524
- if (deps['@angular/core']) stats.frameworks.push('Angular')
525
- if (deps.svelte) stats.frameworks.push('Svelte')
526
-
527
- // Detect languages
528
- if (pkg.devDependencies?.typescript || (await this.fileExists('tsconfig.json'))) {
529
- stats.languages.push('TypeScript')
530
- } else {
531
- stats.languages.push('JavaScript')
532
- }
533
- } catch (error) {
534
- log.debug('No package.json found', { path: this.projectPath, error: getErrorMessage(error) })
535
- }
536
-
537
- // Check other ecosystems
538
- if (await this.fileExists('Cargo.toml')) {
539
- stats.ecosystem = 'Rust'
540
- stats.languages.push('Rust')
541
- }
542
- if (await this.fileExists('go.mod')) {
543
- stats.ecosystem = 'Go'
544
- stats.languages.push('Go')
545
- }
546
- if ((await this.fileExists('requirements.txt')) || (await this.fileExists('pyproject.toml'))) {
547
- stats.ecosystem = 'Python'
548
- stats.languages.push('Python')
549
- }
550
-
551
- // Determine project type
552
- if (stats.fileCount > 300 || stats.frameworks.length >= 3) {
553
- stats.projectType = 'enterprise'
554
- } else if (stats.fileCount > 50 || stats.frameworks.length >= 2) {
555
- stats.projectType = 'complex'
556
- }
557
-
558
- return stats
559
- }
560
-
561
- // ==========================================================================
562
- // COMMAND DETECTION
563
- // ==========================================================================
564
-
565
- private async detectCommands(): Promise<ProjectCommands> {
566
- const commands: ProjectCommands = {
567
- install: 'npm install',
568
- run: 'npm run',
569
- test: 'npm test',
570
- build: 'npm run build',
571
- dev: 'npm run dev',
572
- lint: 'npm run lint',
573
- format: 'npm run format',
574
- }
575
-
576
- // Detect package manager
577
- if (await this.fileExists('bun.lockb')) {
578
- commands.install = 'bun install'
579
- commands.run = 'bun run'
580
- commands.test = 'bun test'
581
- commands.build = 'bun run build'
582
- commands.dev = 'bun run dev'
583
- commands.lint = 'bun run lint'
584
- commands.format = 'bun run format'
585
- } else if (await this.fileExists('pnpm-lock.yaml')) {
586
- commands.install = 'pnpm install'
587
- commands.run = 'pnpm run'
588
- commands.test = 'pnpm test'
589
- commands.build = 'pnpm run build'
590
- commands.dev = 'pnpm run dev'
591
- commands.lint = 'pnpm run lint'
592
- commands.format = 'pnpm run format'
593
- } else if (await this.fileExists('yarn.lock')) {
594
- commands.install = 'yarn'
595
- commands.run = 'yarn'
596
- commands.test = 'yarn test'
597
- commands.build = 'yarn build'
598
- commands.dev = 'yarn dev'
599
- commands.lint = 'yarn lint'
600
- commands.format = 'yarn format'
601
- }
602
-
603
- // Non-JS ecosystems
604
- if (await this.fileExists('Cargo.toml')) {
605
- commands.install = 'cargo build'
606
- commands.run = 'cargo run'
607
- commands.test = 'cargo test'
608
- commands.build = 'cargo build --release'
609
- commands.dev = 'cargo run'
610
- commands.lint = 'cargo clippy'
611
- commands.format = 'cargo fmt'
612
- }
613
-
614
- if (await this.fileExists('go.mod')) {
615
- commands.install = 'go mod download'
616
- commands.run = 'go run .'
617
- commands.test = 'go test ./...'
618
- commands.build = 'go build'
619
- commands.dev = 'go run .'
620
- commands.lint = 'golangci-lint run'
621
- commands.format = 'go fmt ./...'
622
- }
623
-
624
- return commands
625
- }
626
-
627
- // ==========================================================================
628
- // SOURCE CITATIONS
629
- // ==========================================================================
630
-
631
- private buildSources(stats: ProjectStats, commands: ProjectCommands): ContextSources {
632
- const sources = defaultSources()
633
-
634
- // Determine ecosystem source file
635
- const ecosystemFiles: Record<string, string> = {
636
- JavaScript: 'package.json',
637
- Rust: 'Cargo.toml',
638
- Go: 'go.mod',
639
- Python: 'pyproject.toml',
640
- }
641
- const ecosystemFile = ecosystemFiles[stats.ecosystem] || 'filesystem'
642
- const detected = (file: string): SourceInfo => ({ file, type: 'detected' })
643
- const inferred = (file: string): SourceInfo => ({ file, type: 'inferred' })
644
-
645
- sources.ecosystem = detected(ecosystemFile)
646
- sources.name = detected(ecosystemFile)
647
- sources.version = detected(ecosystemFile)
648
- sources.languages = detected(ecosystemFile)
649
- sources.frameworks = detected(ecosystemFile)
650
-
651
- // Commands source is the lock file or ecosystem file
652
- if (commands.install.startsWith('bun')) {
653
- sources.commands = detected('bun.lockb')
654
- } else if (commands.install.startsWith('pnpm')) {
655
- sources.commands = detected('pnpm-lock.yaml')
656
- } else if (commands.install === 'yarn') {
657
- sources.commands = detected('yarn.lock')
658
- } else if (commands.install.startsWith('cargo')) {
659
- sources.commands = detected('Cargo.toml')
660
- } else if (commands.install.startsWith('go')) {
661
- sources.commands = detected('go.mod')
662
- } else {
663
- sources.commands = detected('package.json')
664
- }
665
-
666
- // Project type is inferred from file count + framework count
667
- sources.projectType = inferred('file count + frameworks')
668
-
669
- // Git is always from git
670
- sources.git = detected('git')
671
-
672
- return sources
673
- }
674
-
675
- // ==========================================================================
676
- // STACK DETECTION
677
- // ==========================================================================
678
-
679
- private async detectStack(): Promise<StackDetection> {
680
- const detector = new StackDetector(this.projectPath)
681
- return detector.detect()
682
- }
683
-
684
- // ==========================================================================
685
- // AGENT GENERATION
686
- // ==========================================================================
687
-
688
- private async generateAgents(
689
- stack: StackDetection,
690
- stats: ProjectStats,
691
- feedbackContext?: {
692
- patternsDiscovered: string[]
693
- knownGotchas: string[]
694
- agentAccuracy: Array<{ agent: string; rating: string; note?: string }>
695
- }
696
- ): Promise<SyncAgentInfo[]> {
697
- this.taskFeedbackContext = feedbackContext
698
- const agents: SyncAgentInfo[] = []
699
- const agentsPath = path.join(this.globalPath, 'agents')
700
-
701
- // Purge old agents
702
- try {
703
- const files = await fs.readdir(agentsPath)
704
- for (const file of files) {
705
- if (file.endsWith('.md')) {
706
- await fs.unlink(path.join(agentsPath, file))
707
- }
708
- }
709
- } catch (error) {
710
- log.debug('Failed to purge old agents', { path: agentsPath, error: getErrorMessage(error) })
711
- }
712
-
713
- // Workflow agents (always generated) - IN PARALLEL
714
- const workflowAgents = ['prjct-workflow', 'prjct-planner', 'prjct-shipper']
715
- await Promise.all(workflowAgents.map((name) => this.generateWorkflowAgent(name, agentsPath)))
716
- for (const name of workflowAgents) {
717
- agents.push({ name, type: 'workflow' })
718
- }
719
-
720
- // Domain agents (based on stack) - COLLECT AND GENERATE IN PARALLEL
721
- const domainAgentsToGenerate: { name: string; skill?: string }[] = []
722
-
723
- if (stack.hasFrontend) {
724
- domainAgentsToGenerate.push({ name: 'frontend', skill: 'javascript-typescript' })
725
- domainAgentsToGenerate.push({ name: 'uxui', skill: 'frontend-design' })
726
- }
727
- if (stack.hasBackend) {
728
- domainAgentsToGenerate.push({ name: 'backend', skill: 'javascript-typescript' })
729
- }
730
- if (stack.hasDatabase) {
731
- domainAgentsToGenerate.push({ name: 'database' })
732
- }
733
- if (stack.hasTesting) {
734
- domainAgentsToGenerate.push({ name: 'testing', skill: 'developer-kit' })
735
- }
736
- if (stack.hasDocker) {
737
- domainAgentsToGenerate.push({ name: 'devops', skill: 'developer-kit' })
738
- }
739
-
740
- // Generate all domain agents IN PARALLEL
741
- await Promise.all(
742
- domainAgentsToGenerate.map((agent) =>
743
- this.generateDomainAgent(agent.name, agentsPath, stats, stack)
744
- )
745
- )
746
-
747
- // Add to agents list
748
- for (const agent of domainAgentsToGenerate) {
749
- agents.push({ name: agent.name, type: 'domain', skill: agent.skill })
750
- }
751
-
752
- return agents
753
- }
754
-
755
- /**
756
- * Load existing agent info from disk (for incremental sync when agents don't need regeneration).
757
- * Reads the agents directory and returns metadata without regenerating files.
758
- */
759
- private async loadExistingAgents(): Promise<SyncAgentInfo[]> {
760
- const agentsPath = path.join(this.globalPath, 'agents')
761
- const agents: SyncAgentInfo[] = []
762
-
763
- try {
764
- const files = await fs.readdir(agentsPath)
765
- const workflowNames = new Set(['prjct-workflow', 'prjct-planner', 'prjct-shipper'])
766
-
767
- for (const file of files) {
768
- if (!file.endsWith('.md')) continue
769
- const name = file.replace('.md', '')
770
- const type = workflowNames.has(name) ? ('workflow' as const) : ('domain' as const)
771
- agents.push({ name, type })
772
- }
773
- } catch {
774
- // No existing agents — fall back to generation
775
- return []
776
- }
777
-
778
- return agents
779
- }
780
-
781
- /**
782
- * Resolve {{> partial-name }} includes in template content.
783
- * Loads partials from templates/subagents/.
784
- */
785
- private async resolveTemplateIncludes(content: string): Promise<string> {
786
- const includePattern = /\{\{>\s*([\w-]+)\s*\}\}/g
787
- const matches = [...content.matchAll(includePattern)]
788
-
789
- if (matches.length === 0) return content
790
-
791
- let resolved = content
792
- for (const match of matches) {
793
- const partialName = match[1]
794
- const partialPath = path.join(
795
- __dirname,
796
- '..',
797
- '..',
798
- 'templates',
799
- 'subagents',
800
- `${partialName}.md`
801
- )
802
- try {
803
- const partialContent = await fs.readFile(partialPath, 'utf-8')
804
- resolved = resolved.replace(match[0], partialContent.trim())
805
- } catch {
806
- // Partial not found — leave marker for debugging
807
- resolved = resolved.replace(match[0], `<!-- partial "${partialName}" not found -->`)
808
- }
809
- }
810
-
811
- return resolved
812
- }
813
-
814
- private async generateWorkflowAgent(name: string, agentsPath: string): Promise<void> {
815
- // Try to read template
816
- let content = ''
817
- try {
818
- const templatePath = path.join(
819
- __dirname,
820
- '..',
821
- '..',
822
- 'templates',
823
- 'subagents',
824
- 'workflow',
825
- `${name}.md`
826
- )
827
- content = await fs.readFile(templatePath, 'utf-8')
828
- content = await this.resolveTemplateIncludes(content)
829
- } catch (error) {
830
- log.debug('Workflow agent template not found, generating minimal', {
831
- name,
832
- error: getErrorMessage(error),
833
- })
834
- content = this.generateMinimalWorkflowAgent(name)
835
- }
836
-
837
- await fs.writeFile(path.join(agentsPath, `${name}.md`), content, 'utf-8')
838
- }
839
-
840
- private async generateDomainAgent(
841
- name: string,
842
- agentsPath: string,
843
- stats: ProjectStats,
844
- stack: StackDetection
845
- ): Promise<void> {
846
- // Try to read template
847
- let content = ''
848
- try {
849
- const templatePath = path.join(
850
- __dirname,
851
- '..',
852
- '..',
853
- 'templates',
854
- 'subagents',
855
- 'domain',
856
- `${name}.md`
857
- )
858
- content = await fs.readFile(templatePath, 'utf-8')
859
-
860
- // Resolve includes before variable replacement
861
- content = await this.resolveTemplateIncludes(content)
862
-
863
- // Inject project-specific context
864
- content = content.replace('{projectName}', stats.name)
865
- content = content.replace('{frameworks}', stack.frameworks.join(', ') || 'None detected')
866
- content = content.replace('{ecosystem}', stats.ecosystem)
867
- } catch (error) {
868
- log.debug('Domain agent template not found, generating minimal', {
869
- name,
870
- error: getErrorMessage(error),
871
- })
872
- content = this.generateMinimalDomainAgent(name, stats, stack)
873
- }
874
-
875
- // Inject task feedback learnings (PRJ-272)
876
- content = this.injectFeedbackSection(content, name)
877
-
878
- await fs.writeFile(path.join(agentsPath, `${name}.md`), content, 'utf-8')
879
- }
880
-
881
- /**
882
- * Inject a "Recent Learnings" section into agent content from task feedback (PRJ-272)
883
- */
884
- private injectFeedbackSection(content: string, agentName: string): string {
885
- if (!this.taskFeedbackContext) return content
886
-
887
- const { patternsDiscovered, knownGotchas, agentAccuracy } = this.taskFeedbackContext
888
-
889
- const agentNotes = agentAccuracy.filter(
890
- (a) => a.agent === `${agentName}.md` || a.agent === agentName
891
- )
892
-
893
- const hasContent =
894
- patternsDiscovered.length > 0 || knownGotchas.length > 0 || agentNotes.length > 0
895
- if (!hasContent) return content
896
-
897
- const lines: string[] = ['\n## Recent Learnings (from completed tasks)\n']
898
-
899
- if (patternsDiscovered.length > 0) {
900
- lines.push('### Discovered Patterns')
901
- for (const pattern of patternsDiscovered) {
902
- lines.push(`- ${pattern}`)
903
- }
904
- lines.push('')
905
- }
906
-
907
- if (knownGotchas.length > 0) {
908
- lines.push('### Known Gotchas')
909
- for (const gotcha of knownGotchas) {
910
- lines.push(`- ${gotcha}`)
911
- }
912
- lines.push('')
913
- }
914
-
915
- if (agentNotes.length > 0) {
916
- lines.push('### Agent Accuracy Notes')
917
- for (const note of agentNotes) {
918
- const desc = note.note ? ` — ${note.note}` : ''
919
- lines.push(`- ${note.rating}${desc}`)
920
- }
921
- lines.push('')
922
- }
923
-
924
- return content + lines.join('\n')
925
- }
926
-
927
- private generateMinimalWorkflowAgent(name: string): string {
928
- const descriptions: Record<string, string> = {
929
- 'prjct-workflow': 'Task lifecycle: now, done, pause, resume',
930
- 'prjct-planner': 'Planning: task, prd, spec, bug',
931
- 'prjct-shipper': 'Shipping: ship, merge, review',
932
- }
933
-
934
- return `---
935
- name: ${name}
936
- description: ${descriptions[name] || 'Workflow agent'}
937
- tools: Read, Write, Glob
938
- ---
939
-
940
- # ${name.toUpperCase()}
941
-
942
- Workflow agent for prjct operations.
943
-
944
- ## Project Context
945
-
946
- When invoked:
947
- 1. Read \`.prjct/prjct.config.json\` → extract \`projectId\`
948
- 2. Read \`~/.prjct-cli/projects/{projectId}/storage/state.json\`
949
- 3. Execute requested operation
950
- `
951
- }
952
-
953
- private generateMinimalDomainAgent(
954
- name: string,
955
- stats: ProjectStats,
956
- stack: StackDetection
957
- ): string {
958
- return `---
959
- name: ${name}
960
- description: ${name.charAt(0).toUpperCase() + name.slice(1)} specialist for ${stats.name}
961
- tools: Read, Write, Glob, Grep
962
- skills: []
963
- ---
964
-
965
- # ${name.toUpperCase()} AGENT
966
-
967
- Domain specialist for ${name} tasks.
968
-
969
- ## Project Context
970
-
971
- - **Project**: ${stats.name}
972
- - **Ecosystem**: ${stats.ecosystem}
973
- - **Frameworks**: ${stack.frameworks.join(', ') || 'None detected'}
974
-
975
- ## Your Role
976
-
977
- You are the ${name} expert for this project. Apply best practices for the detected stack.
978
- `
979
- }
980
-
981
- // ==========================================================================
982
- // SKILL CONFIGURATION
983
- // ==========================================================================
984
-
985
- private configureSkills(agents: SyncAgentInfo[]): { agent: string; skill: string }[] {
986
- const skills: { agent: string; skill: string }[] = []
987
-
988
- for (const agent of agents) {
989
- if (agent.skill) {
990
- skills.push({ agent: agent.name, skill: agent.skill })
991
- }
992
- }
993
-
994
- // Write skills.json
995
- const skillsConfig = {
996
- projectId: this.projectId,
997
- syncedAt: dateHelper.getTimestamp(),
998
- skills: skills.map((s) => ({
999
- name: s.skill,
1000
- linkedAgents: [s.agent],
1001
- })),
1002
- agentSkillMap: Object.fromEntries(skills.map((s) => [s.agent, s.skill])),
1003
- }
1004
-
1005
- fs.writeFile(
1006
- path.join(this.globalPath, 'config', 'skills.json'),
1007
- JSON.stringify(skillsConfig, null, 2),
1008
- 'utf-8'
1009
- ).catch((error) => {
1010
- log.debug('Failed to write skills.json', { error: getErrorMessage(error) })
1011
- })
1012
-
1013
- return skills
1014
- }
1015
-
1016
- // ==========================================================================
1017
- // SKILL AUTO-INSTALLATION
1018
- // ==========================================================================
1019
-
1020
- /**
1021
- * Auto-install skills from skill-mappings.json for generated agents.
1022
- * Reads the mapping, checks which packages are needed, and installs missing ones.
1023
- */
1024
- private async autoInstallSkills(
1025
- agents: SyncAgentInfo[]
1026
- ): Promise<{ name: string; agent: string; status: 'installed' | 'skipped' | 'error' }[]> {
1027
- const results: { name: string; agent: string; status: 'installed' | 'skipped' | 'error' }[] = []
1028
-
1029
- try {
1030
- // Load skill mappings
1031
- const mappingsPath = path.join(
1032
- __dirname,
1033
- '..',
1034
- '..',
1035
- 'templates',
1036
- 'config',
1037
- 'skill-mappings.json'
1038
- )
1039
- const mappingsContent = await fs.readFile(mappingsPath, 'utf-8')
1040
- const mappings = JSON.parse(mappingsContent)
1041
- const agentToSkillMap = mappings.agentToSkillMap || {}
1042
-
1043
- // Collect all packages to install, grouped by agent
1044
- const packagesToInstall: { pkg: string; agent: string }[] = []
1045
- for (const agent of agents) {
1046
- const mapping = agentToSkillMap[agent.name]
1047
- if (mapping?.packages) {
1048
- for (const pkg of mapping.packages) {
1049
- packagesToInstall.push({ pkg, agent: agent.name })
1050
- }
1051
- }
1052
- }
1053
-
1054
- if (packagesToInstall.length === 0) return results
1055
-
1056
- // Install each package (check if already installed first)
1057
- const skillsDir = path.join(os.homedir(), '.claude', 'skills')
1058
- for (const { pkg, agent } of packagesToInstall) {
1059
- // Extract skill name from package path (e.g., "anthropics/skills/frontend-design" -> "frontend-design")
1060
- const skillName = pkg.split('/').pop() || pkg
1061
-
1062
- // Check if already installed
1063
- const subdirPath = path.join(skillsDir, skillName, 'SKILL.md')
1064
- const flatPath = path.join(skillsDir, `${skillName}.md`)
1065
-
1066
- let alreadyInstalled = false
1067
- try {
1068
- await fs.access(subdirPath)
1069
- alreadyInstalled = true
1070
- } catch {
1071
- try {
1072
- await fs.access(flatPath)
1073
- alreadyInstalled = true
1074
- } catch {
1075
- // Not installed
1076
- }
1077
- }
1078
-
1079
- if (alreadyInstalled) {
1080
- results.push({ name: skillName, agent, status: 'skipped' })
1081
- continue
1082
- }
1083
-
1084
- // Install via skillInstaller (supports owner/repo format)
1085
- try {
1086
- // Parse package as owner/repo or owner/repo@skill format
1087
- // "anthropics/skills/frontend-design" -> owner=anthropics, repo=skills, skill=frontend-design
1088
- const parts = pkg.split('/')
1089
- let installSource: string
1090
- if (parts.length === 3) {
1091
- // owner/repo/skill -> owner/repo@skill
1092
- installSource = `${parts[0]}/${parts[1]}@${parts[2]}`
1093
- } else {
1094
- installSource = pkg
1095
- }
1096
-
1097
- const installResult = await skillInstaller.install(installSource)
1098
- if (installResult.installed.length > 0) {
1099
- results.push({ name: skillName, agent, status: 'installed' })
1100
- log.info(`Installed skill: ${skillName} for agent: ${agent}`)
1101
- } else if (installResult.errors.length > 0) {
1102
- results.push({ name: skillName, agent, status: 'error' })
1103
- log.debug(`Failed to install skill ${skillName}`, { errors: installResult.errors })
1104
- } else {
1105
- results.push({ name: skillName, agent, status: 'skipped' })
1106
- }
1107
- } catch (error) {
1108
- results.push({ name: skillName, agent, status: 'error' })
1109
- log.debug(`Skill install error for ${skillName}`, { error: getErrorMessage(error) })
1110
- }
1111
- }
1112
- } catch (error) {
1113
- log.debug('Skill auto-installation failed (non-critical)', { error: getErrorMessage(error) })
1114
- }
1115
-
1116
- return results
1117
- }
1118
-
1119
- // ==========================================================================
1120
- // CONTEXT FILE GENERATION
1121
- // ==========================================================================
1122
-
1123
- private async generateContextFiles(
1124
- git: GitData,
1125
- stats: ProjectStats,
1126
- commands: ProjectCommands,
1127
- agents: SyncAgentInfo[],
1128
- sources?: ContextSources
1129
- ): Promise<string[]> {
1130
- const generator = new ContextFileGenerator({
1131
- projectId: this.projectId!,
1132
- projectPath: this.projectPath,
1133
- globalPath: this.globalPath,
1134
- })
1135
-
1136
- return generator.generate(git, stats, commands, agents, sources)
1137
- }
1138
-
1139
- // ==========================================================================
1140
- // PROJECT.JSON UPDATE
1141
- // ==========================================================================
1142
-
1143
- private async updateProjectJson(git: GitData, stats: ProjectStats): Promise<void> {
1144
- const projectJsonPath = path.join(this.globalPath, 'project.json')
1145
-
1146
- // Read existing
1147
- let existing: Record<string, unknown> = {}
1148
- try {
1149
- existing = JSON.parse(await fs.readFile(projectJsonPath, 'utf-8'))
1150
- } catch (error) {
1151
- log.debug('No existing project.json', {
1152
- path: projectJsonPath,
1153
- error: getErrorMessage(error),
1154
- })
1155
- }
1156
-
1157
- const updated = {
1158
- ...existing,
1159
- projectId: this.projectId,
1160
- repoPath: this.projectPath,
1161
- name: stats.name,
1162
- version: stats.version,
1163
- cliVersion: this.cliVersion,
1164
- techStack: stats.frameworks,
1165
- fileCount: stats.fileCount,
1166
- commitCount: git.commits,
1167
- stack: stats.ecosystem,
1168
- currentBranch: git.branch,
1169
- hasUncommittedChanges: git.hasChanges,
1170
- createdAt: existing.createdAt || dateHelper.getTimestamp(),
1171
- lastSync: dateHelper.getTimestamp(),
1172
- // Staleness tracking (PRJ-120)
1173
- lastSyncCommit: git.recentCommits[0]?.hash || null,
1174
- lastSyncBranch: git.branch,
1175
- }
1176
-
1177
- await fs.writeFile(projectJsonPath, JSON.stringify(updated, null, 2), 'utf-8')
1178
- }
1179
-
1180
- // ==========================================================================
1181
- // STATE.JSON UPDATE
1182
- // ==========================================================================
1183
-
1184
- private async updateStateJson(stats: ProjectStats, stack: StackDetection): Promise<void> {
1185
- const statePath = path.join(this.globalPath, 'storage', 'state.json')
1186
-
1187
- // Read existing
1188
- let state: Record<string, unknown> = {}
1189
- try {
1190
- state = JSON.parse(await fs.readFile(statePath, 'utf-8'))
1191
- } catch (error) {
1192
- log.debug('No existing state.json', { path: statePath, error: getErrorMessage(error) })
1193
- }
1194
-
1195
- // Update with enterprise fields
1196
- state.projectId = this.projectId
1197
- state.stack = {
1198
- language: stats.languages[0] || 'Unknown',
1199
- framework: stats.frameworks[0] || null,
1200
- }
1201
- state.domains = {
1202
- hasFrontend: stack.hasFrontend,
1203
- hasBackend: stack.hasBackend,
1204
- hasDatabase: stack.hasDatabase,
1205
- hasTesting: stack.hasTesting,
1206
- hasDocker: stack.hasDocker,
1207
- }
1208
- state.projectType = stats.projectType
1209
- state.metrics = {
1210
- totalFiles: stats.fileCount,
1211
- }
1212
- state.lastSync = dateHelper.getTimestamp()
1213
- state.lastUpdated = dateHelper.getTimestamp()
1214
- state.context = {
1215
- ...((state.context as Record<string, unknown>) || {}),
1216
- lastSession: dateHelper.getTimestamp(),
1217
- lastAction: 'Synced project',
1218
- nextAction: 'Run `p. task "description"` to start working',
1219
- }
1220
-
1221
- await fs.writeFile(statePath, JSON.stringify(state, null, 2), 'utf-8')
1222
-
1223
- // Also generate local .prjct-state.md (PRJ-112)
1224
- try {
1225
- await localStateGenerator.generate(
1226
- this.projectPath,
1227
- state as import('../schemas/state').StateJson
1228
- )
1229
- } catch (error) {
1230
- log.debug('Local state generation failed (optional)', { error: getErrorMessage(error) })
1231
- }
1232
- }
1233
-
1234
- // ==========================================================================
1235
- // MEMORY LOGGING
1236
- // ==========================================================================
1237
-
1238
- private async logToMemory(git: GitData, stats: ProjectStats): Promise<void> {
1239
- const memoryPath = path.join(this.globalPath, 'memory', 'events.jsonl')
1240
-
1241
- const event = {
1242
- ts: dateHelper.getTimestamp(),
1243
- action: 'sync',
1244
- branch: git.branch,
1245
- uncommitted: git.hasChanges,
1246
- fileCount: stats.fileCount,
1247
- commitCount: git.commits,
1248
- }
1249
-
1250
- await fs.appendFile(memoryPath, `${JSON.stringify(event)}\n`, 'utf-8')
1251
- }
1252
-
1253
- // ==========================================================================
1254
- // METRICS RECORDING
1255
- // ==========================================================================
1256
-
1257
- /**
1258
- * Record sync metrics for the value dashboard
1259
- *
1260
- * Calculates token savings by comparing:
1261
- * - Original: Estimated tokens if we sent all source files
1262
- * - Filtered: Actual tokens in generated context files
1263
- *
1264
- * Token estimation: ~4 chars per token (industry standard)
1265
- */
1266
- private async recordSyncMetrics(
1267
- stats: ProjectStats,
1268
- contextFiles: string[],
1269
- agents: SyncAgentInfo[],
1270
- duration: number
1271
- ): Promise<SyncMetrics> {
1272
- const CHARS_PER_TOKEN = 4
1273
-
1274
- // Calculate filtered size (actual context files generated)
1275
- let filteredChars = 0
1276
- for (const file of contextFiles) {
1277
- try {
1278
- const filePath = path.join(this.globalPath, file)
1279
- const content = await fs.readFile(filePath, 'utf-8')
1280
- filteredChars += content.length
1281
- } catch (error) {
1282
- log.debug('Context file not found for metrics', { file, error: getErrorMessage(error) })
1283
- }
1284
- }
1285
-
1286
- // Also count agent files
1287
- for (const agent of agents) {
1288
- try {
1289
- const agentPath = path.join(this.globalPath, 'agents', `${agent.name}.md`)
1290
- const content = await fs.readFile(agentPath, 'utf-8')
1291
- filteredChars += content.length
1292
- } catch (error) {
1293
- log.debug('Agent file not found for metrics', {
1294
- agent: agent.name,
1295
- error: getErrorMessage(error),
1296
- })
1297
- }
1298
- }
1299
-
1300
- const filteredSize = Math.floor(filteredChars / CHARS_PER_TOKEN)
1301
-
1302
- // Estimate original size (what it would take without prjct)
1303
- // Conservative estimate: avg 500 tokens per source file
1304
- // Plus overhead for manually creating context
1305
- const avgTokensPerFile = 500
1306
- const originalSize = stats.fileCount * avgTokensPerFile
1307
-
1308
- // Calculate compression rate
1309
- const compressionRate =
1310
- originalSize > 0 ? Math.max(0, (originalSize - filteredSize) / originalSize) : 0
1311
-
1312
- // Record to storage
1313
- try {
1314
- await metricsStorage.recordSync(this.projectId!, {
1315
- originalSize,
1316
- filteredSize,
1317
- duration,
1318
- isWatch: false,
1319
- agents: agents.filter((a) => a.type === 'domain').map((a) => a.name),
1320
- })
1321
- } catch (error) {
1322
- log.debug('Failed to record sync metrics', { error: getErrorMessage(error) })
1323
- }
1324
-
1325
- return {
1326
- duration,
1327
- originalSize,
1328
- filteredSize,
1329
- compressionRate,
1330
- }
1331
- }
1332
-
1333
- // ==========================================================================
1334
- // DRAFT ANALYSIS (PRJ-263)
1335
- // ==========================================================================
1336
-
1337
- /**
1338
- * Save sync results as a draft analysis.
1339
- * Preserves existing sealed analysis — only the draft is overwritten.
1340
- * Incorporates task feedback from completed tasks (PRJ-272).
1341
- */
1342
- private async saveDraftAnalysis(
1343
- git: GitData,
1344
- stats: ProjectStats,
1345
- _stack: StackDetection
1346
- ): Promise<void> {
1347
- try {
1348
- const commitHash = git.recentCommits[0]?.hash || null
1349
-
1350
- // Load aggregated feedback from completed tasks (PRJ-272)
1351
- let patterns: Array<{ name: string; description: string; location?: string }> = []
1352
- let antiPatterns: Array<{ issue: string; file: string; suggestion: string }> = []
1353
- try {
1354
- const feedback = await stateStorage.getAggregatedFeedback(this.projectId!)
1355
-
1356
- // Convert discovered patterns to CodePattern objects
1357
- if (feedback.patternsDiscovered.length > 0) {
1358
- patterns = feedback.patternsDiscovered.map((p) => ({
1359
- name: p,
1360
- description: `Discovered during task execution: ${p}`,
1361
- }))
1362
- }
1363
-
1364
- // Convert known gotchas (recurring issues) to AntiPattern objects
1365
- if (feedback.knownGotchas.length > 0) {
1366
- antiPatterns = feedback.knownGotchas.map((g) => ({
1367
- issue: g,
1368
- file: 'multiple',
1369
- suggestion: `Recurring issue reported across tasks: ${g}`,
1370
- }))
1371
- }
1372
- } catch {
1373
- // Feedback aggregation failure should not block analysis
1374
- }
1375
-
1376
- await analysisStorage.saveDraft(this.projectId!, {
1377
- projectId: this.projectId!,
1378
- languages: stats.languages,
1379
- frameworks: stats.frameworks,
1380
- configFiles: [],
1381
- fileCount: stats.fileCount,
1382
- patterns,
1383
- antiPatterns,
1384
- analyzedAt: dateHelper.getTimestamp(),
1385
- status: 'draft',
1386
- commitHash: commitHash ?? undefined,
1387
- })
1388
- } catch (error) {
1389
- log.debug('Failed to save draft analysis (non-critical)', { error: getErrorMessage(error) })
1390
- }
1391
- }
1392
-
1393
- // ==========================================================================
1394
- // ARCHIVAL (PRJ-267)
1395
- // ==========================================================================
1396
-
1397
- /**
1398
- * Archive stale data across all storage types.
1399
- * Runs during sync to keep active storage lean.
1400
- */
1401
- private async archiveStaleData(): Promise<void> {
1402
- if (!this.projectId) return
1403
-
1404
- try {
1405
- const [shipped, dormant, staleQueue, stalePaused, memoryCapped] = await Promise.all([
1406
- shippedStorage.archiveOldShipped(this.projectId).catch(() => 0),
1407
- ideasStorage.markDormantIdeas(this.projectId).catch(() => 0),
1408
- queueStorage.removeStaleCompleted(this.projectId).catch(() => 0),
1409
- stateStorage.archiveStalePausedTasks(this.projectId).catch(() => []),
1410
- memoryService.capEntries(this.projectId).catch(() => 0),
1411
- ])
1412
-
1413
- const totalArchived =
1414
- shipped + dormant + staleQueue + (stalePaused as unknown[]).length + memoryCapped
1415
-
1416
- if (totalArchived > 0) {
1417
- log.info('Archived stale data', {
1418
- shipped,
1419
- dormant,
1420
- staleQueue,
1421
- stalePaused: (stalePaused as unknown[]).length,
1422
- memoryCapped,
1423
- total: totalArchived,
1424
- })
1425
-
1426
- // Record archive stats
1427
- const stats = archiveStorage.getStats(this.projectId)
1428
- log.debug('Archive stats', stats)
1429
- }
1430
- } catch (error) {
1431
- log.debug('Archival failed (non-critical)', { error: getErrorMessage(error) })
1432
- }
1433
- }
1434
-
1435
- // ==========================================================================
1436
- // HELPERS
1437
- // ==========================================================================
1438
-
1439
- private async fileExists(filename: string): Promise<boolean> {
1440
- try {
1441
- await fs.access(path.join(this.projectPath, filename))
1442
- return true
1443
- } catch (error) {
1444
- log.debug('File not found', { filename, error: getErrorMessage(error) })
1445
- return false
1446
- }
1447
- }
1448
-
1449
- private async getCliVersion(): Promise<string> {
1450
- try {
1451
- // Try to read from package.json in the module
1452
- const pkgPath = path.join(__dirname, '..', '..', 'package.json')
1453
- const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf-8'))
1454
- return pkg.version || '0.0.0'
1455
- } catch (error) {
1456
- log.debug('Failed to read CLI version', { error: getErrorMessage(error) })
1457
- return '0.0.0'
1458
- }
1459
- }
1460
-
1461
- // Empty data structures for error cases
1462
- private emptyGitData(): GitData {
1463
- return {
1464
- branch: 'main',
1465
- commits: 0,
1466
- contributors: 0,
1467
- hasChanges: false,
1468
- stagedFiles: [],
1469
- modifiedFiles: [],
1470
- untrackedFiles: [],
1471
- recentCommits: [],
1472
- weeklyCommits: 0,
1473
- }
1474
- }
1475
-
1476
- private emptyStats(): ProjectStats {
1477
- return {
1478
- fileCount: 0,
1479
- version: '0.0.0',
1480
- name: 'unknown',
1481
- ecosystem: 'unknown',
1482
- projectType: 'simple',
1483
- languages: [],
1484
- frameworks: [],
1485
- }
1486
- }
1487
-
1488
- private emptyCommands(): ProjectCommands {
1489
- return {
1490
- install: 'npm install',
1491
- run: 'npm run',
1492
- test: 'npm test',
1493
- build: 'npm run build',
1494
- dev: 'npm run dev',
1495
- lint: 'npm run lint',
1496
- format: 'npm run format',
1497
- }
1498
- }
1499
-
1500
- private emptyStack(): StackDetection {
1501
- return {
1502
- hasFrontend: false,
1503
- hasBackend: false,
1504
- hasDatabase: false,
1505
- hasDocker: false,
1506
- hasTesting: false,
1507
- frontendType: null,
1508
- frameworks: [],
1509
- }
1510
- }
1511
- }
1512
-
1513
- export const syncService = new SyncService()
1514
- export { SyncService }
1515
- export type { ProjectSyncResult as SyncResult } from '../types'