prjct-cli 0.20.0 → 0.20.1

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 (226) hide show
  1. package/CHANGELOG.md +24 -6
  2. package/CLAUDE.md +56 -15
  3. package/README.md +5 -6
  4. package/bin/prjct +59 -42
  5. package/bin/prjct.ts +60 -0
  6. package/core/__tests__/agentic/memory-system.test.ts +18 -3
  7. package/core/__tests__/agentic/plan-mode.test.ts +55 -26
  8. package/core/__tests__/agentic/prompt-builder.test.ts +6 -6
  9. package/core/__tests__/utils/project-commands.test.ts +72 -0
  10. package/core/agentic/agent-router.ts +3 -12
  11. package/core/agentic/command-executor.ts +372 -3
  12. package/core/agentic/context-builder.ts +7 -27
  13. package/core/agentic/ground-truth.ts +604 -5
  14. package/core/agentic/index.ts +180 -0
  15. package/core/agentic/loop-detector.ts +418 -4
  16. package/core/agentic/memory-system.ts +857 -3
  17. package/core/agentic/plan-mode.ts +491 -4
  18. package/core/agentic/prompt-builder.ts +44 -65
  19. package/core/agentic/services.ts +13 -5
  20. package/core/agentic/skill-loader.ts +112 -0
  21. package/core/agentic/smart-context.ts +37 -122
  22. package/core/agentic/template-loader.ts +79 -122
  23. package/core/agentic/tool-registry.ts +5 -11
  24. package/core/agents/index.ts +1 -1
  25. package/core/agents/performance.ts +4 -2
  26. package/core/bus/bus.ts +262 -0
  27. package/core/bus/index.ts +3 -313
  28. package/core/commands/analysis.ts +5 -5
  29. package/core/commands/analytics.ts +11 -11
  30. package/core/commands/base.ts +33 -209
  31. package/core/commands/cleanup.ts +148 -0
  32. package/core/commands/command-data.ts +346 -0
  33. package/core/commands/commands.ts +216 -0
  34. package/core/commands/design.ts +83 -0
  35. package/core/commands/index.ts +13 -207
  36. package/core/commands/maintenance.ts +52 -473
  37. package/core/commands/planning.ts +3 -3
  38. package/core/commands/register.ts +104 -0
  39. package/core/commands/registry.ts +441 -0
  40. package/core/commands/setup.ts +25 -9
  41. package/core/commands/shipping.ts +48 -11
  42. package/core/commands/snapshots.ts +299 -0
  43. package/core/commands/workflow.ts +2 -2
  44. package/core/constants/index.ts +254 -4
  45. package/core/domain/agent-loader.ts +5 -6
  46. package/core/domain/task-stack.ts +555 -4
  47. package/core/errors.ts +127 -1
  48. package/core/events/events.ts +87 -0
  49. package/core/events/index.ts +4 -138
  50. package/core/index.ts +15 -23
  51. package/core/infrastructure/agent-detector.ts +126 -201
  52. package/core/infrastructure/author-detector.ts +99 -171
  53. package/core/infrastructure/command-installer.ts +476 -4
  54. package/core/infrastructure/config-manager.ts +41 -37
  55. package/core/infrastructure/path-manager.ts +59 -9
  56. package/core/infrastructure/permission-manager.ts +286 -0
  57. package/core/outcomes/analyzer.ts +7 -41
  58. package/core/outcomes/index.ts +1 -1
  59. package/core/outcomes/recorder.ts +1 -1
  60. package/core/{plugins → plugin/builtin}/webhook.ts +6 -22
  61. package/core/plugin/loader.ts +5 -5
  62. package/core/plugin/registry.ts +2 -2
  63. package/core/schemas/ideas.ts +85 -54
  64. package/core/schemas/index.ts +14 -33
  65. package/core/schemas/permissions.ts +177 -0
  66. package/core/schemas/project.ts +39 -12
  67. package/core/schemas/roadmap.ts +94 -59
  68. package/core/schemas/schemas.ts +39 -0
  69. package/core/schemas/shipped.ts +87 -60
  70. package/core/schemas/state.ts +110 -70
  71. package/core/server/index.ts +21 -0
  72. package/core/server/routes.ts +165 -0
  73. package/core/server/server.ts +136 -0
  74. package/core/server/sse.ts +135 -0
  75. package/core/services/agent-service.ts +170 -0
  76. package/core/services/breakdown-service.ts +126 -0
  77. package/core/services/index.ts +21 -0
  78. package/core/services/memory-service.ts +108 -0
  79. package/core/services/project-service.ts +146 -0
  80. package/core/services/skill-service.ts +253 -0
  81. package/core/session/compaction.ts +257 -0
  82. package/core/session/index.ts +20 -8
  83. package/core/{infrastructure/session-manager/migration.ts → session/log-migration.ts} +9 -9
  84. package/core/{infrastructure/session-manager/session-manager.ts → session/session-log-manager.ts} +27 -26
  85. package/core/session/{session-manager.ts → task-session-manager.ts} +7 -4
  86. package/core/session/utils.ts +1 -1
  87. package/core/storage/ideas-storage.ts +10 -26
  88. package/core/storage/index.ts +14 -162
  89. package/core/storage/queue-storage.ts +13 -11
  90. package/core/storage/shipped-storage.ts +4 -17
  91. package/core/storage/state-storage.ts +35 -43
  92. package/core/storage/storage-manager.ts +42 -52
  93. package/core/storage/storage.ts +160 -0
  94. package/core/sync/auth-config.ts +1 -8
  95. package/core/sync/index.ts +17 -10
  96. package/core/sync/oauth-handler.ts +1 -6
  97. package/core/sync/sync-client.ts +6 -34
  98. package/core/sync/sync-manager.ts +11 -40
  99. package/core/types/agentic.ts +577 -0
  100. package/core/types/agents.ts +145 -0
  101. package/core/types/bus.ts +82 -0
  102. package/core/types/commands.ts +366 -0
  103. package/core/types/config.ts +66 -0
  104. package/core/types/core.ts +96 -0
  105. package/core/types/domain.ts +71 -0
  106. package/core/types/events.ts +42 -0
  107. package/core/types/fs.ts +56 -0
  108. package/core/types/index.ts +387 -500
  109. package/core/types/infrastructure.ts +196 -0
  110. package/core/{agentic/memory-system/types.ts → types/memory.ts} +33 -8
  111. package/core/{outcomes/types.ts → types/outcomes.ts} +53 -8
  112. package/core/types/plugin.ts +25 -0
  113. package/core/types/server.ts +54 -0
  114. package/core/types/services.ts +65 -0
  115. package/core/types/session.ts +135 -0
  116. package/core/types/storage.ts +148 -0
  117. package/core/types/sync.ts +121 -0
  118. package/core/types/task.ts +72 -0
  119. package/core/types/template.ts +24 -0
  120. package/core/types/utils.ts +90 -0
  121. package/core/utils/cache.ts +195 -0
  122. package/core/utils/collection-filters.ts +245 -0
  123. package/core/utils/date-helper.ts +1 -5
  124. package/core/utils/file-helper.ts +20 -10
  125. package/core/utils/jsonl-helper.ts +5 -8
  126. package/core/utils/markdown-builder.ts +277 -0
  127. package/core/utils/project-commands.ts +132 -0
  128. package/core/utils/runtime.ts +119 -0
  129. package/dist/bin/prjct.mjs +12568 -0
  130. package/package.json +13 -8
  131. package/scripts/build.js +106 -0
  132. package/scripts/postinstall.js +50 -8
  133. package/templates/agentic/subagent-generation.md +1 -1
  134. package/templates/commands/serve.md +118 -0
  135. package/templates/commands/ship.md +13 -2
  136. package/templates/commands/skill.md +110 -0
  137. package/templates/commands/sync.md +1 -1
  138. package/templates/commands/test.md +23 -4
  139. package/templates/permissions/default.jsonc +60 -0
  140. package/templates/permissions/permissive.jsonc +49 -0
  141. package/templates/permissions/strict.jsonc +62 -0
  142. package/templates/skills/code-review.md +47 -0
  143. package/templates/skills/debug.md +61 -0
  144. package/templates/skills/refactor.md +47 -0
  145. package/templates/subagents/domain/devops.md +1 -1
  146. package/templates/subagents/domain/testing.md +6 -10
  147. package/templates/subagents/workflow/prjct-shipper.md +16 -7
  148. package/templates/tools/bash.txt +22 -0
  149. package/templates/tools/edit.txt +18 -0
  150. package/templates/tools/glob.txt +19 -0
  151. package/templates/tools/grep.txt +21 -0
  152. package/templates/tools/read.txt +14 -0
  153. package/templates/tools/task.txt +20 -0
  154. package/templates/tools/webfetch.txt +16 -0
  155. package/templates/tools/websearch.txt +18 -0
  156. package/templates/tools/write.txt +17 -0
  157. package/core/agentic/command-executor/command-executor.ts +0 -312
  158. package/core/agentic/command-executor/index.ts +0 -16
  159. package/core/agentic/command-executor/status-signal.ts +0 -38
  160. package/core/agentic/command-executor/types.ts +0 -79
  161. package/core/agentic/ground-truth/index.ts +0 -76
  162. package/core/agentic/ground-truth/types.ts +0 -33
  163. package/core/agentic/ground-truth/utils.ts +0 -48
  164. package/core/agentic/ground-truth/verifiers/analyze.ts +0 -54
  165. package/core/agentic/ground-truth/verifiers/done.ts +0 -75
  166. package/core/agentic/ground-truth/verifiers/feature.ts +0 -70
  167. package/core/agentic/ground-truth/verifiers/index.ts +0 -37
  168. package/core/agentic/ground-truth/verifiers/init.ts +0 -52
  169. package/core/agentic/ground-truth/verifiers/now.ts +0 -57
  170. package/core/agentic/ground-truth/verifiers/ship.ts +0 -85
  171. package/core/agentic/ground-truth/verifiers/spec.ts +0 -45
  172. package/core/agentic/ground-truth/verifiers/sync.ts +0 -47
  173. package/core/agentic/ground-truth/verifiers.ts +0 -6
  174. package/core/agentic/loop-detector/error-analysis.ts +0 -97
  175. package/core/agentic/loop-detector/hallucination.ts +0 -71
  176. package/core/agentic/loop-detector/index.ts +0 -41
  177. package/core/agentic/loop-detector/loop-detector.ts +0 -222
  178. package/core/agentic/loop-detector/types.ts +0 -66
  179. package/core/agentic/memory-system/history.ts +0 -53
  180. package/core/agentic/memory-system/index.ts +0 -192
  181. package/core/agentic/memory-system/patterns.ts +0 -156
  182. package/core/agentic/memory-system/semantic-memories.ts +0 -278
  183. package/core/agentic/memory-system/session.ts +0 -21
  184. package/core/agentic/plan-mode/approval.ts +0 -57
  185. package/core/agentic/plan-mode/constants.ts +0 -44
  186. package/core/agentic/plan-mode/index.ts +0 -28
  187. package/core/agentic/plan-mode/plan-mode.ts +0 -407
  188. package/core/agentic/plan-mode/types.ts +0 -193
  189. package/core/agents/types.ts +0 -126
  190. package/core/command-registry/categories.ts +0 -23
  191. package/core/command-registry/commands.ts +0 -15
  192. package/core/command-registry/core-commands.ts +0 -344
  193. package/core/command-registry/index.ts +0 -158
  194. package/core/command-registry/optional-commands.ts +0 -163
  195. package/core/command-registry/setup-commands.ts +0 -83
  196. package/core/command-registry/types.ts +0 -59
  197. package/core/command-registry.ts +0 -9
  198. package/core/commands/types.ts +0 -185
  199. package/core/commands.ts +0 -11
  200. package/core/constants/formats.ts +0 -187
  201. package/core/context-sync.ts +0 -18
  202. package/core/data/index.ts +0 -27
  203. package/core/data/md-base-manager.ts +0 -203
  204. package/core/data/md-ideas-manager.ts +0 -155
  205. package/core/data/md-queue-manager.ts +0 -180
  206. package/core/data/md-shipped-manager.ts +0 -90
  207. package/core/data/md-state-manager.ts +0 -137
  208. package/core/domain/task-stack/index.ts +0 -19
  209. package/core/domain/task-stack/parser.ts +0 -86
  210. package/core/domain/task-stack/storage.ts +0 -123
  211. package/core/domain/task-stack/task-stack.ts +0 -340
  212. package/core/domain/task-stack/types.ts +0 -51
  213. package/core/infrastructure/command-installer/command-installer.ts +0 -327
  214. package/core/infrastructure/command-installer/global-config.ts +0 -136
  215. package/core/infrastructure/command-installer/index.ts +0 -25
  216. package/core/infrastructure/command-installer/types.ts +0 -41
  217. package/core/infrastructure/session-manager/index.ts +0 -23
  218. package/core/infrastructure/session-manager/types.ts +0 -45
  219. package/core/infrastructure/session-manager.ts +0 -8
  220. package/core/serializers/ideas-serializer.ts +0 -187
  221. package/core/serializers/index.ts +0 -36
  222. package/core/serializers/queue-serializer.ts +0 -210
  223. package/core/serializers/shipped-serializer.ts +0 -108
  224. package/core/serializers/state-serializer.ts +0 -136
  225. package/core/session/types.ts +0 -29
  226. /package/core/infrastructure/{agents/claude-agent.ts → claude-agent.ts} +0 -0
@@ -15,14 +15,7 @@ import crypto from 'crypto'
15
15
  import os from 'os'
16
16
  import * as dateHelper from '../utils/date-helper'
17
17
  import * as fileHelper from '../utils/file-helper'
18
-
19
- interface SessionInfo {
20
- year: string
21
- month: string
22
- day: string
23
- path: string
24
- date: Date
25
- }
18
+ import type { SessionInfo } from '../types'
26
19
 
27
20
  class PathManager {
28
21
  globalBaseDir: string
@@ -30,7 +23,21 @@ class PathManager {
30
23
  globalConfigDir: string
31
24
 
32
25
  constructor() {
33
- this.globalBaseDir = path.join(os.homedir(), '.prjct-cli')
26
+ const envOverride = process.env.PRJCT_CLI_HOME?.trim()
27
+ this.globalBaseDir = envOverride ? path.resolve(envOverride) : path.join(os.homedir(), '.prjct-cli')
28
+ this.globalProjectsDir = path.join(this.globalBaseDir, 'projects')
29
+ this.globalConfigDir = path.join(this.globalBaseDir, 'config')
30
+ }
31
+
32
+ /**
33
+ * Override global storage location (primarily for tests and sandboxed environments).
34
+ *
35
+ * When unset, global storage defaults to `~/.prjct-cli/`.
36
+ *
37
+ * @param {string} globalBaseDir - Base directory that will contain `projects/` and `config/`.
38
+ */
39
+ setGlobalBaseDir(globalBaseDir: string): void {
40
+ this.globalBaseDir = path.resolve(globalBaseDir)
34
41
  this.globalProjectsDir = path.join(this.globalBaseDir, 'projects')
35
42
  this.globalConfigDir = path.join(this.globalBaseDir, 'config')
36
43
  }
@@ -274,6 +281,49 @@ class PathManager {
274
281
  getLastSyncPath(projectId: string): string {
275
282
  return path.join(this.getGlobalProjectPath(projectId), 'sync', 'last-sync.json')
276
283
  }
284
+
285
+ /**
286
+ * Get the running status file path (for status signal)
287
+ * Used to indicate when prjct CLI is actively running
288
+ */
289
+ getRunningStatusPath(): string {
290
+ return path.join(this.globalBaseDir, '.running')
291
+ }
292
+
293
+ /**
294
+ * Get the docs directory path
295
+ * Contains documentation and help files
296
+ */
297
+ getDocsPath(): string {
298
+ return path.join(this.globalBaseDir, 'docs')
299
+ }
300
+
301
+ /**
302
+ * Get the agents directory path for a project
303
+ * If projectId is null, returns the global agents directory
304
+ */
305
+ getAgentsPath(projectId: string | null): string {
306
+ if (projectId) {
307
+ return path.join(this.getGlobalProjectPath(projectId), 'agents')
308
+ }
309
+ return path.join(this.globalBaseDir, 'agents')
310
+ }
311
+
312
+ /**
313
+ * Get the storage file path for a project
314
+ * Convenience method for accessing storage layer files
315
+ */
316
+ getStoragePath(projectId: string, filename: string): string {
317
+ return path.join(this.getGlobalProjectPath(projectId), 'storage', filename)
318
+ }
319
+
320
+ /**
321
+ * Get the context directory path for a project
322
+ * Contains generated markdown context files
323
+ */
324
+ getContextPath(projectId: string): string {
325
+ return path.join(this.getGlobalProjectPath(projectId), 'context')
326
+ }
277
327
  }
278
328
 
279
329
  const pathManager = new PathManager()
@@ -0,0 +1,286 @@
1
+ /**
2
+ * PermissionManager - Granular permission control for CLI operations
3
+ *
4
+ * Implements glob-based permission matching inspired by opencode.
5
+ * Checks bash commands, file operations, and web access against
6
+ * configurable permission rules.
7
+ *
8
+ * @version 1.0.0
9
+ */
10
+
11
+ import {
12
+ type PermissionsConfig,
13
+ type PermissionLevel,
14
+ buildDefaultPermissions,
15
+ } from '../schemas/permissions'
16
+ import type { PermissionCheckResult } from '../types'
17
+
18
+ /**
19
+ * Simple glob pattern matching
20
+ * Supports * (any chars) and ? (single char)
21
+ */
22
+ function matchGlobPattern(pattern: string, text: string): boolean {
23
+ // Escape regex special chars except * and ?
24
+ const regexPattern = pattern
25
+ .replace(/[.+^${}()|[\]\\]/g, '\\$&')
26
+ .replace(/\*/g, '.*')
27
+ .replace(/\?/g, '.')
28
+
29
+ const regex = new RegExp(`^${regexPattern}$`, 'i')
30
+ return regex.test(text)
31
+ }
32
+
33
+ /**
34
+ * Find the most specific matching pattern
35
+ * More specific = longer pattern without wildcards
36
+ */
37
+ function findBestMatch(
38
+ patterns: Record<string, PermissionLevel>,
39
+ text: string
40
+ ): { pattern: string; level: PermissionLevel } | null {
41
+ let bestMatch: { pattern: string; level: PermissionLevel; specificity: number } | null = null
42
+
43
+ for (const [pattern, level] of Object.entries(patterns)) {
44
+ if (matchGlobPattern(pattern, text)) {
45
+ // Calculate specificity: longer patterns without wildcards are more specific
46
+ const wildcardCount = (pattern.match(/\*/g) || []).length
47
+ const specificity = pattern.length - wildcardCount * 10
48
+
49
+ if (!bestMatch || specificity > bestMatch.specificity) {
50
+ bestMatch = { pattern, level, specificity }
51
+ }
52
+ }
53
+ }
54
+
55
+ return bestMatch ? { pattern: bestMatch.pattern, level: bestMatch.level } : null
56
+ }
57
+
58
+ class PermissionManager {
59
+ private config: PermissionsConfig
60
+
61
+ constructor(config?: PermissionsConfig) {
62
+ this.config = config || buildDefaultPermissions()
63
+ }
64
+
65
+ /**
66
+ * Update the permissions configuration
67
+ */
68
+ setConfig(config: PermissionsConfig): void {
69
+ this.config = config
70
+ }
71
+
72
+ /**
73
+ * Merge custom permissions with defaults
74
+ */
75
+ mergeWithDefaults(custom: Partial<PermissionsConfig>): PermissionsConfig {
76
+ const defaults = buildDefaultPermissions()
77
+ return {
78
+ ...defaults,
79
+ ...custom,
80
+ bash: { ...defaults.bash, ...custom.bash },
81
+ files: {
82
+ read: { ...defaults.files?.read, ...custom.files?.read },
83
+ write: { ...defaults.files?.write, ...custom.files?.write },
84
+ delete: { ...defaults.files?.delete, ...custom.files?.delete },
85
+ },
86
+ web: {
87
+ enabled: custom.web?.enabled ?? defaults.web?.enabled ?? true,
88
+ allowedDomains: custom.web?.allowedDomains ?? defaults.web?.allowedDomains,
89
+ blockedDomains: custom.web?.blockedDomains ?? defaults.web?.blockedDomains,
90
+ },
91
+ doomLoop: {
92
+ enabled: custom.doomLoop?.enabled ?? defaults.doomLoop?.enabled ?? true,
93
+ maxRetries: custom.doomLoop?.maxRetries ?? defaults.doomLoop?.maxRetries ?? 3,
94
+ },
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Check if a bash command is allowed
100
+ */
101
+ checkBashCommand(command: string): PermissionCheckResult {
102
+ if (!this.config.bash) {
103
+ return { allowed: true, level: 'allow', reason: 'No bash permissions configured' }
104
+ }
105
+
106
+ const match = findBestMatch(this.config.bash, command)
107
+
108
+ if (!match) {
109
+ // Default: allow if no pattern matches
110
+ return { allowed: true, level: 'allow', reason: 'No matching pattern' }
111
+ }
112
+
113
+ return {
114
+ allowed: match.level === 'allow',
115
+ level: match.level,
116
+ matchedPattern: match.pattern,
117
+ reason: match.level === 'deny'
118
+ ? `Command denied by pattern: ${match.pattern}`
119
+ : match.level === 'ask'
120
+ ? `Command requires approval: ${match.pattern}`
121
+ : undefined,
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Check if a file operation is allowed
127
+ */
128
+ checkFileOperation(
129
+ operation: 'read' | 'write' | 'delete',
130
+ filePath: string
131
+ ): PermissionCheckResult {
132
+ const filePerms = this.config.files?.[operation]
133
+
134
+ if (!filePerms) {
135
+ return { allowed: true, level: 'allow', reason: 'No file permissions configured' }
136
+ }
137
+
138
+ const match = findBestMatch(filePerms, filePath)
139
+
140
+ if (!match) {
141
+ return { allowed: true, level: 'allow', reason: 'No matching pattern' }
142
+ }
143
+
144
+ return {
145
+ allowed: match.level === 'allow',
146
+ level: match.level,
147
+ matchedPattern: match.pattern,
148
+ reason: match.level === 'deny'
149
+ ? `File operation denied: ${operation} on ${match.pattern}`
150
+ : match.level === 'ask'
151
+ ? `File operation requires approval: ${operation}`
152
+ : undefined,
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Check if web fetch is allowed for a domain
158
+ */
159
+ checkWebFetch(url: string): PermissionCheckResult {
160
+ const webConfig = this.config.web
161
+
162
+ if (!webConfig?.enabled) {
163
+ return {
164
+ allowed: false,
165
+ level: 'deny',
166
+ reason: 'Web fetch is disabled',
167
+ }
168
+ }
169
+
170
+ try {
171
+ const domain = new URL(url).hostname
172
+
173
+ // Check blocked domains
174
+ if (webConfig.blockedDomains?.some((d) => domain.includes(d))) {
175
+ return {
176
+ allowed: false,
177
+ level: 'deny',
178
+ matchedPattern: domain,
179
+ reason: `Domain is blocked: ${domain}`,
180
+ }
181
+ }
182
+
183
+ // Check allowed domains (if specified, only those are allowed)
184
+ if (webConfig.allowedDomains && webConfig.allowedDomains.length > 0) {
185
+ const isAllowed = webConfig.allowedDomains.some((d) => domain.includes(d))
186
+ if (!isAllowed) {
187
+ return {
188
+ allowed: false,
189
+ level: 'deny',
190
+ matchedPattern: domain,
191
+ reason: `Domain not in allowed list: ${domain}`,
192
+ }
193
+ }
194
+ }
195
+
196
+ return { allowed: true, level: 'allow' }
197
+ } catch {
198
+ return {
199
+ allowed: false,
200
+ level: 'deny',
201
+ reason: 'Invalid URL',
202
+ }
203
+ }
204
+ }
205
+
206
+ /**
207
+ * Check if a skill can be invoked
208
+ */
209
+ checkSkill(skillName: string): PermissionCheckResult {
210
+ if (!this.config.skills) {
211
+ return { allowed: true, level: 'allow', reason: 'No skill permissions configured' }
212
+ }
213
+
214
+ const match = findBestMatch(this.config.skills, skillName)
215
+
216
+ if (!match) {
217
+ return { allowed: true, level: 'allow', reason: 'No matching pattern' }
218
+ }
219
+
220
+ return {
221
+ allowed: match.level === 'allow',
222
+ level: match.level,
223
+ matchedPattern: match.pattern,
224
+ }
225
+ }
226
+
227
+ /**
228
+ * Check if external directory access is allowed
229
+ */
230
+ checkExternalDirectory(path: string, projectRoot: string): PermissionCheckResult {
231
+ const isExternal = !path.startsWith(projectRoot)
232
+
233
+ if (!isExternal) {
234
+ return { allowed: true, level: 'allow', reason: 'Path is within project' }
235
+ }
236
+
237
+ const level = this.config.externalDirectories || 'ask'
238
+
239
+ return {
240
+ allowed: level === 'allow',
241
+ level,
242
+ reason: level === 'deny'
243
+ ? 'External directory access denied'
244
+ : level === 'ask'
245
+ ? 'External directory access requires approval'
246
+ : undefined,
247
+ }
248
+ }
249
+
250
+ /**
251
+ * Get current permissions config
252
+ */
253
+ getConfig(): PermissionsConfig {
254
+ return this.config
255
+ }
256
+
257
+ /**
258
+ * Check doom loop protection
259
+ */
260
+ checkDoomLoop(retryCount: number): PermissionCheckResult {
261
+ const doomLoop = this.config.doomLoop
262
+
263
+ if (!doomLoop?.enabled) {
264
+ return { allowed: true, level: 'allow', reason: 'Doom loop protection disabled' }
265
+ }
266
+
267
+ const maxRetries = doomLoop.maxRetries || 3
268
+
269
+ if (retryCount >= maxRetries) {
270
+ return {
271
+ allowed: false,
272
+ level: 'deny',
273
+ reason: `Doom loop detected: ${retryCount} retries exceeded limit of ${maxRetries}`,
274
+ }
275
+ }
276
+
277
+ return { allowed: true, level: 'allow' }
278
+ }
279
+ }
280
+
281
+ // Singleton instance
282
+ const permissionManager = new PermissionManager()
283
+ export default permissionManager
284
+
285
+ // Export class for testing
286
+ export { PermissionManager }
@@ -6,47 +6,13 @@
6
6
  */
7
7
 
8
8
  import outcomeRecorder from './recorder'
9
- import type { Outcome, OutcomeSummary, QualityScore } from './types'
10
-
11
- /**
12
- * Pattern detected from outcomes.
13
- */
14
- export interface DetectedPattern {
15
- /** Pattern description */
16
- description: string
17
-
18
- /** Confidence level (0-1) */
19
- confidence: number
20
-
21
- /** Number of occurrences supporting this pattern */
22
- occurrences: number
23
-
24
- /** Suggested action based on pattern */
25
- suggestedAction?: string
26
- }
27
-
28
- /**
29
- * Agent performance metrics.
30
- */
31
- export interface AgentMetrics {
32
- /** Agent name */
33
- agent: string
34
-
35
- /** Number of tasks completed */
36
- tasksCompleted: number
37
-
38
- /** Success rate (0-100) */
39
- successRate: number
40
-
41
- /** Average quality score */
42
- avgQualityScore: number
43
-
44
- /** Estimate accuracy */
45
- estimateAccuracy: number
46
-
47
- /** Best task types for this agent */
48
- bestFor: string[]
49
- }
9
+ import type {
10
+ Outcome,
11
+ OutcomeSummary,
12
+ QualityScore,
13
+ DetectedPattern,
14
+ AgentMetrics,
15
+ } from '../types'
50
16
 
51
17
  /**
52
18
  * OutcomeAnalyzer - Extracts insights from outcomes.
@@ -31,4 +31,4 @@
31
31
 
32
32
  export { OutcomeRecorder, default as outcomeRecorder } from './recorder'
33
33
  export { OutcomeAnalyzer, default as outcomeAnalyzer } from './analyzer'
34
- export * from './types'
34
+ export * from '../types'
@@ -9,7 +9,7 @@ import path from 'path'
9
9
  import * as fileHelper from '../utils/file-helper'
10
10
  import pathManager from '../infrastructure/path-manager'
11
11
  import { generateUUID } from '../schemas'
12
- import type { Outcome, OutcomeInput, OutcomeFilter } from './types'
12
+ import type { Outcome, OutcomeInput, OutcomeFilter } from '../types'
13
13
 
14
14
  const OUTCOMES_DIR = 'outcomes'
15
15
  const OUTCOMES_FILE = 'outcomes.jsonl'
@@ -18,25 +18,9 @@
18
18
  */
19
19
 
20
20
  import crypto from 'crypto'
21
- import { EventTypes } from '../bus'
22
- import { HookPoints } from '../plugin/hooks'
23
-
24
- interface WebhookConfig {
25
- url?: string
26
- events?: string[]
27
- secret?: string
28
- }
29
-
30
- interface PluginContext {
31
- config: WebhookConfig
32
- }
33
-
34
- interface WebhookPayload {
35
- event: string
36
- timestamp: string
37
- source: string
38
- data: unknown
39
- }
21
+ import { EventTypes } from '../../bus'
22
+ import { HookPoints } from '../hooks'
23
+ import type { WebhookConfig, WebhookPluginContext, WebhookPayload } from '../../types'
40
24
 
41
25
  const plugin = {
42
26
  name: 'webhook',
@@ -46,12 +30,12 @@ const plugin = {
46
30
  // Plugin state
47
31
  config: null as WebhookConfig | null,
48
32
  enabled: false,
49
- events: [] as string[],
33
+ enabledEvents: [] as string[],
50
34
 
51
35
  /**
52
36
  * Activate plugin
53
37
  */
54
- async activate({ config }: PluginContext): Promise<void> {
38
+ async activate({ config }: WebhookPluginContext): Promise<void> {
55
39
  plugin.config = config
56
40
 
57
41
  if (!config.url) {
@@ -60,7 +44,7 @@ const plugin = {
60
44
  }
61
45
 
62
46
  plugin.enabled = true
63
- plugin.events = config.events || [
47
+ plugin.enabledEvents = config.events || [
64
48
  EventTypes.SESSION_COMPLETED,
65
49
  EventTypes.FEATURE_SHIPPED,
66
50
  EventTypes.SNAPSHOT_CREATED
@@ -12,7 +12,7 @@
12
12
  import fs from 'fs/promises'
13
13
  import path from 'path'
14
14
  import { hookSystem } from './hooks'
15
- import { eventBus } from '../bus'
15
+ import { eventBus, type EventCallback } from '../bus'
16
16
  import pathManager from '../infrastructure/path-manager'
17
17
 
18
18
  type PluginSource = 'builtin' | 'global' | 'project'
@@ -23,7 +23,7 @@ interface Plugin {
23
23
  version?: string
24
24
  description?: string
25
25
  hooks?: Record<string, HookHandler>
26
- events?: Record<string, HookHandler>
26
+ events?: Record<string, EventCallback>
27
27
  commands?: Record<string, { handler: () => void; description?: string }>
28
28
  priority?: number
29
29
  activate?: (context: PluginContext) => Promise<void>
@@ -83,10 +83,10 @@ class PluginLoader {
83
83
  }
84
84
 
85
85
  /**
86
- * Load built-in plugins from core/plugins
86
+ * Load built-in plugins from core/plugin/builtin
87
87
  */
88
88
  async loadBuiltinPlugins(): Promise<void> {
89
- const builtinPath = path.join(__dirname, '..', 'plugins')
89
+ const builtinPath = path.join(__dirname, 'builtin')
90
90
 
91
91
  try {
92
92
  const files = await fs.readdir(builtinPath)
@@ -106,7 +106,7 @@ class PluginLoader {
106
106
  * Load global plugins from ~/.prjct-cli/plugins
107
107
  */
108
108
  async loadGlobalPlugins(): Promise<void> {
109
- const globalPath = path.join(pathManager.getGlobalStoragePath(), 'plugins')
109
+ const globalPath = path.join(pathManager.getGlobalBasePath(), 'plugins')
110
110
 
111
111
  try {
112
112
  const files = await fs.readdir(globalPath)
@@ -57,7 +57,7 @@ class PluginRegistry {
57
57
 
58
58
  // Global plugins
59
59
  await this.discoverFromPath(
60
- path.join(pathManager.getGlobalStoragePath(), 'plugins'),
60
+ path.join(pathManager.getGlobalBasePath(), 'plugins'),
61
61
  'global'
62
62
  )
63
63
  }
@@ -188,7 +188,7 @@ class PluginRegistry {
188
188
  * Install a plugin (copy to global plugins)
189
189
  */
190
190
  async install(sourcePath: string, name: string | null = null): Promise<void> {
191
- const globalPluginsPath = path.join(pathManager.getGlobalStoragePath(), 'plugins')
191
+ const globalPluginsPath = path.join(pathManager.getGlobalBasePath(), 'plugins')
192
192
  await fs.mkdir(globalPluginsPath, { recursive: true })
193
193
 
194
194
  const stat = await fs.stat(sourcePath)