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.
- package/CHANGELOG.md +24 -6
- package/CLAUDE.md +56 -15
- package/README.md +5 -6
- package/bin/prjct +59 -42
- package/bin/prjct.ts +60 -0
- package/core/__tests__/agentic/memory-system.test.ts +18 -3
- package/core/__tests__/agentic/plan-mode.test.ts +55 -26
- package/core/__tests__/agentic/prompt-builder.test.ts +6 -6
- package/core/__tests__/utils/project-commands.test.ts +72 -0
- package/core/agentic/agent-router.ts +3 -12
- package/core/agentic/command-executor.ts +372 -3
- package/core/agentic/context-builder.ts +7 -27
- package/core/agentic/ground-truth.ts +604 -5
- package/core/agentic/index.ts +180 -0
- package/core/agentic/loop-detector.ts +418 -4
- package/core/agentic/memory-system.ts +857 -3
- package/core/agentic/plan-mode.ts +491 -4
- package/core/agentic/prompt-builder.ts +44 -65
- package/core/agentic/services.ts +13 -5
- package/core/agentic/skill-loader.ts +112 -0
- package/core/agentic/smart-context.ts +37 -122
- package/core/agentic/template-loader.ts +79 -122
- package/core/agentic/tool-registry.ts +5 -11
- package/core/agents/index.ts +1 -1
- package/core/agents/performance.ts +4 -2
- package/core/bus/bus.ts +262 -0
- package/core/bus/index.ts +3 -313
- package/core/commands/analysis.ts +5 -5
- package/core/commands/analytics.ts +11 -11
- package/core/commands/base.ts +33 -209
- package/core/commands/cleanup.ts +148 -0
- package/core/commands/command-data.ts +346 -0
- package/core/commands/commands.ts +216 -0
- package/core/commands/design.ts +83 -0
- package/core/commands/index.ts +13 -207
- package/core/commands/maintenance.ts +52 -473
- package/core/commands/planning.ts +3 -3
- package/core/commands/register.ts +104 -0
- package/core/commands/registry.ts +441 -0
- package/core/commands/setup.ts +25 -9
- package/core/commands/shipping.ts +48 -11
- package/core/commands/snapshots.ts +299 -0
- package/core/commands/workflow.ts +2 -2
- package/core/constants/index.ts +254 -4
- package/core/domain/agent-loader.ts +5 -6
- package/core/domain/task-stack.ts +555 -4
- package/core/errors.ts +127 -1
- package/core/events/events.ts +87 -0
- package/core/events/index.ts +4 -138
- package/core/index.ts +15 -23
- package/core/infrastructure/agent-detector.ts +126 -201
- package/core/infrastructure/author-detector.ts +99 -171
- package/core/infrastructure/command-installer.ts +476 -4
- package/core/infrastructure/config-manager.ts +41 -37
- package/core/infrastructure/path-manager.ts +59 -9
- package/core/infrastructure/permission-manager.ts +286 -0
- package/core/outcomes/analyzer.ts +7 -41
- package/core/outcomes/index.ts +1 -1
- package/core/outcomes/recorder.ts +1 -1
- package/core/{plugins → plugin/builtin}/webhook.ts +6 -22
- package/core/plugin/loader.ts +5 -5
- package/core/plugin/registry.ts +2 -2
- package/core/schemas/ideas.ts +85 -54
- package/core/schemas/index.ts +14 -33
- package/core/schemas/permissions.ts +177 -0
- package/core/schemas/project.ts +39 -12
- package/core/schemas/roadmap.ts +94 -59
- package/core/schemas/schemas.ts +39 -0
- package/core/schemas/shipped.ts +87 -60
- package/core/schemas/state.ts +110 -70
- package/core/server/index.ts +21 -0
- package/core/server/routes.ts +165 -0
- package/core/server/server.ts +136 -0
- package/core/server/sse.ts +135 -0
- package/core/services/agent-service.ts +170 -0
- package/core/services/breakdown-service.ts +126 -0
- package/core/services/index.ts +21 -0
- package/core/services/memory-service.ts +108 -0
- package/core/services/project-service.ts +146 -0
- package/core/services/skill-service.ts +253 -0
- package/core/session/compaction.ts +257 -0
- package/core/session/index.ts +20 -8
- package/core/{infrastructure/session-manager/migration.ts → session/log-migration.ts} +9 -9
- package/core/{infrastructure/session-manager/session-manager.ts → session/session-log-manager.ts} +27 -26
- package/core/session/{session-manager.ts → task-session-manager.ts} +7 -4
- package/core/session/utils.ts +1 -1
- package/core/storage/ideas-storage.ts +10 -26
- package/core/storage/index.ts +14 -162
- package/core/storage/queue-storage.ts +13 -11
- package/core/storage/shipped-storage.ts +4 -17
- package/core/storage/state-storage.ts +35 -43
- package/core/storage/storage-manager.ts +42 -52
- package/core/storage/storage.ts +160 -0
- package/core/sync/auth-config.ts +1 -8
- package/core/sync/index.ts +17 -10
- package/core/sync/oauth-handler.ts +1 -6
- package/core/sync/sync-client.ts +6 -34
- package/core/sync/sync-manager.ts +11 -40
- package/core/types/agentic.ts +577 -0
- package/core/types/agents.ts +145 -0
- package/core/types/bus.ts +82 -0
- package/core/types/commands.ts +366 -0
- package/core/types/config.ts +66 -0
- package/core/types/core.ts +96 -0
- package/core/types/domain.ts +71 -0
- package/core/types/events.ts +42 -0
- package/core/types/fs.ts +56 -0
- package/core/types/index.ts +387 -500
- package/core/types/infrastructure.ts +196 -0
- package/core/{agentic/memory-system/types.ts → types/memory.ts} +33 -8
- package/core/{outcomes/types.ts → types/outcomes.ts} +53 -8
- package/core/types/plugin.ts +25 -0
- package/core/types/server.ts +54 -0
- package/core/types/services.ts +65 -0
- package/core/types/session.ts +135 -0
- package/core/types/storage.ts +148 -0
- package/core/types/sync.ts +121 -0
- package/core/types/task.ts +72 -0
- package/core/types/template.ts +24 -0
- package/core/types/utils.ts +90 -0
- package/core/utils/cache.ts +195 -0
- package/core/utils/collection-filters.ts +245 -0
- package/core/utils/date-helper.ts +1 -5
- package/core/utils/file-helper.ts +20 -10
- package/core/utils/jsonl-helper.ts +5 -8
- package/core/utils/markdown-builder.ts +277 -0
- package/core/utils/project-commands.ts +132 -0
- package/core/utils/runtime.ts +119 -0
- package/dist/bin/prjct.mjs +12568 -0
- package/package.json +13 -8
- package/scripts/build.js +106 -0
- package/scripts/postinstall.js +50 -8
- package/templates/agentic/subagent-generation.md +1 -1
- package/templates/commands/serve.md +118 -0
- package/templates/commands/ship.md +13 -2
- package/templates/commands/skill.md +110 -0
- package/templates/commands/sync.md +1 -1
- package/templates/commands/test.md +23 -4
- package/templates/permissions/default.jsonc +60 -0
- package/templates/permissions/permissive.jsonc +49 -0
- package/templates/permissions/strict.jsonc +62 -0
- package/templates/skills/code-review.md +47 -0
- package/templates/skills/debug.md +61 -0
- package/templates/skills/refactor.md +47 -0
- package/templates/subagents/domain/devops.md +1 -1
- package/templates/subagents/domain/testing.md +6 -10
- package/templates/subagents/workflow/prjct-shipper.md +16 -7
- package/templates/tools/bash.txt +22 -0
- package/templates/tools/edit.txt +18 -0
- package/templates/tools/glob.txt +19 -0
- package/templates/tools/grep.txt +21 -0
- package/templates/tools/read.txt +14 -0
- package/templates/tools/task.txt +20 -0
- package/templates/tools/webfetch.txt +16 -0
- package/templates/tools/websearch.txt +18 -0
- package/templates/tools/write.txt +17 -0
- package/core/agentic/command-executor/command-executor.ts +0 -312
- package/core/agentic/command-executor/index.ts +0 -16
- package/core/agentic/command-executor/status-signal.ts +0 -38
- package/core/agentic/command-executor/types.ts +0 -79
- package/core/agentic/ground-truth/index.ts +0 -76
- package/core/agentic/ground-truth/types.ts +0 -33
- package/core/agentic/ground-truth/utils.ts +0 -48
- package/core/agentic/ground-truth/verifiers/analyze.ts +0 -54
- package/core/agentic/ground-truth/verifiers/done.ts +0 -75
- package/core/agentic/ground-truth/verifiers/feature.ts +0 -70
- package/core/agentic/ground-truth/verifiers/index.ts +0 -37
- package/core/agentic/ground-truth/verifiers/init.ts +0 -52
- package/core/agentic/ground-truth/verifiers/now.ts +0 -57
- package/core/agentic/ground-truth/verifiers/ship.ts +0 -85
- package/core/agentic/ground-truth/verifiers/spec.ts +0 -45
- package/core/agentic/ground-truth/verifiers/sync.ts +0 -47
- package/core/agentic/ground-truth/verifiers.ts +0 -6
- package/core/agentic/loop-detector/error-analysis.ts +0 -97
- package/core/agentic/loop-detector/hallucination.ts +0 -71
- package/core/agentic/loop-detector/index.ts +0 -41
- package/core/agentic/loop-detector/loop-detector.ts +0 -222
- package/core/agentic/loop-detector/types.ts +0 -66
- package/core/agentic/memory-system/history.ts +0 -53
- package/core/agentic/memory-system/index.ts +0 -192
- package/core/agentic/memory-system/patterns.ts +0 -156
- package/core/agentic/memory-system/semantic-memories.ts +0 -278
- package/core/agentic/memory-system/session.ts +0 -21
- package/core/agentic/plan-mode/approval.ts +0 -57
- package/core/agentic/plan-mode/constants.ts +0 -44
- package/core/agentic/plan-mode/index.ts +0 -28
- package/core/agentic/plan-mode/plan-mode.ts +0 -407
- package/core/agentic/plan-mode/types.ts +0 -193
- package/core/agents/types.ts +0 -126
- package/core/command-registry/categories.ts +0 -23
- package/core/command-registry/commands.ts +0 -15
- package/core/command-registry/core-commands.ts +0 -344
- package/core/command-registry/index.ts +0 -158
- package/core/command-registry/optional-commands.ts +0 -163
- package/core/command-registry/setup-commands.ts +0 -83
- package/core/command-registry/types.ts +0 -59
- package/core/command-registry.ts +0 -9
- package/core/commands/types.ts +0 -185
- package/core/commands.ts +0 -11
- package/core/constants/formats.ts +0 -187
- package/core/context-sync.ts +0 -18
- package/core/data/index.ts +0 -27
- package/core/data/md-base-manager.ts +0 -203
- package/core/data/md-ideas-manager.ts +0 -155
- package/core/data/md-queue-manager.ts +0 -180
- package/core/data/md-shipped-manager.ts +0 -90
- package/core/data/md-state-manager.ts +0 -137
- package/core/domain/task-stack/index.ts +0 -19
- package/core/domain/task-stack/parser.ts +0 -86
- package/core/domain/task-stack/storage.ts +0 -123
- package/core/domain/task-stack/task-stack.ts +0 -340
- package/core/domain/task-stack/types.ts +0 -51
- package/core/infrastructure/command-installer/command-installer.ts +0 -327
- package/core/infrastructure/command-installer/global-config.ts +0 -136
- package/core/infrastructure/command-installer/index.ts +0 -25
- package/core/infrastructure/command-installer/types.ts +0 -41
- package/core/infrastructure/session-manager/index.ts +0 -23
- package/core/infrastructure/session-manager/types.ts +0 -45
- package/core/infrastructure/session-manager.ts +0 -8
- package/core/serializers/ideas-serializer.ts +0 -187
- package/core/serializers/index.ts +0 -36
- package/core/serializers/queue-serializer.ts +0 -210
- package/core/serializers/shipped-serializer.ts +0 -108
- package/core/serializers/state-serializer.ts +0 -136
- package/core/session/types.ts +0 -29
- /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
|
-
|
|
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 {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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.
|
package/core/outcomes/index.ts
CHANGED
|
@@ -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 '
|
|
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 '
|
|
22
|
-
import { HookPoints } from '../
|
|
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
|
-
|
|
33
|
+
enabledEvents: [] as string[],
|
|
50
34
|
|
|
51
35
|
/**
|
|
52
36
|
* Activate plugin
|
|
53
37
|
*/
|
|
54
|
-
async activate({ config }:
|
|
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.
|
|
47
|
+
plugin.enabledEvents = config.events || [
|
|
64
48
|
EventTypes.SESSION_COMPLETED,
|
|
65
49
|
EventTypes.FEATURE_SHIPPED,
|
|
66
50
|
EventTypes.SNAPSHOT_CREATED
|
package/core/plugin/loader.ts
CHANGED
|
@@ -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,
|
|
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/
|
|
86
|
+
* Load built-in plugins from core/plugin/builtin
|
|
87
87
|
*/
|
|
88
88
|
async loadBuiltinPlugins(): Promise<void> {
|
|
89
|
-
const builtinPath = path.join(__dirname, '
|
|
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.
|
|
109
|
+
const globalPath = path.join(pathManager.getGlobalBasePath(), 'plugins')
|
|
110
110
|
|
|
111
111
|
try {
|
|
112
112
|
const files = await fs.readdir(globalPath)
|
package/core/plugin/registry.ts
CHANGED
|
@@ -57,7 +57,7 @@ class PluginRegistry {
|
|
|
57
57
|
|
|
58
58
|
// Global plugins
|
|
59
59
|
await this.discoverFromPath(
|
|
60
|
-
path.join(pathManager.
|
|
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.
|
|
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)
|