prjct-cli 0.62.0 → 0.64.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.
- package/CHANGELOG.md +96 -0
- package/core/__tests__/agentic/smart-context.test.ts +372 -0
- package/core/constants/index.ts +54 -0
- package/core/infrastructure/setup.ts +18 -4
- package/core/services/git-analyzer.ts +10 -1
- package/core/services/skill-installer.ts +7 -2
- package/core/sync/sync-client.ts +32 -2
- package/core/types/agentic.ts +68 -5
- package/core/types/index.ts +7 -0
- package/core/types/memory.ts +12 -2
- package/dist/bin/prjct.mjs +53 -6
- package/dist/core/infrastructure/setup.js +43 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,101 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.64.0] - 2026-02-05
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- add input source tagging for context items (PRJ-102) (#106)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## [0.63.2] - 2026-02-05
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Input source tagging (PRJ-102)**: Context items now support origin tracking via `_source` metadata
|
|
15
|
+
|
|
16
|
+
### Implementation Details
|
|
17
|
+
|
|
18
|
+
Added `InputSource` type with 10 standardized source categories (user_explicit, user_file, system_detected, system_generated, system_inferred, learned, external, inherited, cached, unknown). Created `SourceMetadata` interface with capturedAt timestamp and optional confidence/sourcePath fields. Added `SourcedItem` base interface and `createSourceMetadata()` helper function. Extended key context interfaces (AgentInfo, FeatureInfo, PatternInfo, GatheredInfo, LoadedAgent) to support source tracking. Updated MemoryMetadata with inputSource field for consistency.
|
|
19
|
+
|
|
20
|
+
### Learnings
|
|
21
|
+
|
|
22
|
+
- Anthropic pattern: always track origin of all data for traceability and debugging
|
|
23
|
+
- Using optional `_source` field allows backward-compatible adoption
|
|
24
|
+
|
|
25
|
+
### Test Plan
|
|
26
|
+
|
|
27
|
+
#### For QA
|
|
28
|
+
1. Run `bun run typecheck` - passes with new types
|
|
29
|
+
2. Run `bun test` - all 368 tests pass
|
|
30
|
+
3. Verify `InputSource` type is exported from `core/types`
|
|
31
|
+
|
|
32
|
+
#### For Users
|
|
33
|
+
- Context items now support source tracking via `_source` metadata
|
|
34
|
+
- Use `createSourceMetadata()` when creating context items
|
|
35
|
+
- No breaking changes
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
## [0.63.1] - 2026-02-05
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
|
|
42
|
+
- **Unit tests for smart-context.ts (PRJ-84)**: 57 tests covering domain detection, file filtering, and type mappings
|
|
43
|
+
|
|
44
|
+
### Implementation Details
|
|
45
|
+
|
|
46
|
+
Created comprehensive test suite for SmartContext class: `detectDomain()` tests for all 6 domains (frontend, backend, devops, docs, testing, general), `filterFiles()` tests for file pattern matching, `estimateSize()` tests for token estimation, and type mapping function tests.
|
|
47
|
+
|
|
48
|
+
### Learnings
|
|
49
|
+
|
|
50
|
+
- Keyword detection uses substring matching - "be" in "better" matches backend keyword
|
|
51
|
+
- Frontend file pattern `/\.(tsx?|jsx?)$/` also matches plain `.ts` files (known behavior)
|
|
52
|
+
- Confidence scoring caps at 0.95 regardless of keyword count
|
|
53
|
+
|
|
54
|
+
### Test Plan
|
|
55
|
+
|
|
56
|
+
#### For QA
|
|
57
|
+
1. Run `bun test core/__tests__/agentic/smart-context.test.ts` - all 57 tests pass
|
|
58
|
+
2. Run `bun test` - full suite passes (368 tests)
|
|
59
|
+
|
|
60
|
+
#### For Users
|
|
61
|
+
- No user-facing changes (test coverage improvement)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
## [0.63.0] - 2026-02-05
|
|
65
|
+
|
|
66
|
+
### Features
|
|
67
|
+
|
|
68
|
+
- add timeout management with configurable limits (PRJ-111) (#104)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
## [0.62.1] - 2026-02-05
|
|
72
|
+
|
|
73
|
+
### Improved
|
|
74
|
+
|
|
75
|
+
- **Timeout management (PRJ-111)**: Operations now timeout gracefully with configurable limits instead of hanging indefinitely
|
|
76
|
+
|
|
77
|
+
### Implementation Details
|
|
78
|
+
|
|
79
|
+
Added `TIMEOUTS` constants with `getTimeout()` helper function supporting environment variable overrides (`PRJCT_TIMEOUT_*`). Applied timeouts to: npm install (120s), git operations (10s), git clone (60s), and fetch API calls (30s via AbortController). Timeout errors now include helpful hints for increasing limits.
|
|
80
|
+
|
|
81
|
+
### Learnings
|
|
82
|
+
|
|
83
|
+
- AbortController is the standard way to timeout fetch() calls - create controller, set timeout to call abort(), pass signal to fetch
|
|
84
|
+
- Environment variable pattern `PRJCT_TIMEOUT_*` allows user configurability without config files
|
|
85
|
+
|
|
86
|
+
### Test Plan
|
|
87
|
+
|
|
88
|
+
#### For QA
|
|
89
|
+
1. Set `PRJCT_TIMEOUT_GIT_OPERATION=100` (100ms) and run `prjct sync` - should timeout
|
|
90
|
+
2. Unset env var, run `prjct sync` on a large repo - should complete within 10s
|
|
91
|
+
3. Test npm install timeout with `PRJCT_TIMEOUT_NPM_INSTALL=1000` (1s) - should timeout with helpful message
|
|
92
|
+
|
|
93
|
+
#### For Users
|
|
94
|
+
- Operations now timeout gracefully instead of hanging indefinitely
|
|
95
|
+
- Set `PRJCT_TIMEOUT_*` env vars to customize timeouts (e.g., `export PRJCT_TIMEOUT_API_REQUEST=60000` for 60s)
|
|
96
|
+
- No breaking changes
|
|
97
|
+
|
|
98
|
+
|
|
3
99
|
## [0.62.0] - 2026-02-05
|
|
4
100
|
|
|
5
101
|
### Features
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Smart Context Tests
|
|
3
|
+
* PRJ-84: Unit tests for smart-context.ts
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, expect, it } from 'bun:test'
|
|
7
|
+
import smartContext, { SmartContext } from '../../agentic/smart-context'
|
|
8
|
+
import type { ContextDomain, TaskType } from '../../types'
|
|
9
|
+
|
|
10
|
+
describe('SmartContext PRJ-84', () => {
|
|
11
|
+
describe('detectDomain', () => {
|
|
12
|
+
describe('frontend detection', () => {
|
|
13
|
+
it('should detect frontend from UI keywords', () => {
|
|
14
|
+
const result = smartContext.detectDomain('Add a new button component')
|
|
15
|
+
expect(result.primary).toBe('frontend')
|
|
16
|
+
expect(result.confidence).toBeGreaterThan(0.5)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('should detect frontend from React keywords', () => {
|
|
20
|
+
const result = smartContext.detectDomain('Create React component for user profile')
|
|
21
|
+
expect(result.primary).toBe('frontend')
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('should detect frontend from styling keywords', () => {
|
|
25
|
+
const result = smartContext.detectDomain('Fix CSS layout for responsive design')
|
|
26
|
+
expect(result.primary).toBe('frontend')
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('should detect frontend from TSX/JSX keywords', () => {
|
|
30
|
+
const result = smartContext.detectDomain('Update tsx file for modal animation')
|
|
31
|
+
expect(result.primary).toBe('frontend')
|
|
32
|
+
})
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
describe('backend detection', () => {
|
|
36
|
+
it('should detect backend from API keywords', () => {
|
|
37
|
+
const result = smartContext.detectDomain('Create new API endpoint for users')
|
|
38
|
+
expect(result.primary).toBe('backend')
|
|
39
|
+
expect(result.confidence).toBeGreaterThan(0.5)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('should detect backend from database keywords', () => {
|
|
43
|
+
const result = smartContext.detectDomain('Add database query for orders')
|
|
44
|
+
expect(result.primary).toBe('backend')
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('should detect backend from service keywords', () => {
|
|
48
|
+
const result = smartContext.detectDomain('Implement auth service handler')
|
|
49
|
+
expect(result.primary).toBe('backend')
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('should detect backend from GraphQL keywords', () => {
|
|
53
|
+
const result = smartContext.detectDomain('Add GraphQL resolver for products')
|
|
54
|
+
expect(result.primary).toBe('backend')
|
|
55
|
+
})
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
describe('devops detection', () => {
|
|
59
|
+
it('should detect devops from Docker keywords', () => {
|
|
60
|
+
const result = smartContext.detectDomain('Create Docker container for deployment')
|
|
61
|
+
expect(result.primary).toBe('devops')
|
|
62
|
+
expect(result.confidence).toBeGreaterThan(0.5)
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('should detect devops from Kubernetes keywords', () => {
|
|
66
|
+
const result = smartContext.detectDomain('Update k8s deployment configuration')
|
|
67
|
+
expect(result.primary).toBe('devops')
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('should detect devops from CI/CD keywords', () => {
|
|
71
|
+
const result = smartContext.detectDomain('Fix CI pipeline for build process')
|
|
72
|
+
expect(result.primary).toBe('devops')
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('should detect devops from infrastructure keywords', () => {
|
|
76
|
+
const result = smartContext.detectDomain('Configure AWS infrastructure with terraform')
|
|
77
|
+
expect(result.primary).toBe('devops')
|
|
78
|
+
})
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
describe('docs detection', () => {
|
|
82
|
+
it('should detect docs from documentation keywords', () => {
|
|
83
|
+
// Note: "documentation" alone triggers docs domain
|
|
84
|
+
const result = smartContext.detectDomain('Update documentation for users')
|
|
85
|
+
expect(result.primary).toBe('docs')
|
|
86
|
+
expect(result.confidence).toBeGreaterThan(0.5)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('should detect docs from README keywords', () => {
|
|
90
|
+
const result = smartContext.detectDomain('Add readme section for installation')
|
|
91
|
+
expect(result.primary).toBe('docs')
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('should detect docs from changelog keywords', () => {
|
|
95
|
+
const result = smartContext.detectDomain('Update changelog for new release')
|
|
96
|
+
expect(result.primary).toBe('docs')
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it('should detect docs from jsdoc keywords', () => {
|
|
100
|
+
const result = smartContext.detectDomain('Add jsdoc comments to functions')
|
|
101
|
+
expect(result.primary).toBe('docs')
|
|
102
|
+
})
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
describe('testing detection', () => {
|
|
106
|
+
it('should detect testing from test keywords', () => {
|
|
107
|
+
const result = smartContext.detectDomain('Add unit test for user service')
|
|
108
|
+
expect(result.primary).toBe('testing')
|
|
109
|
+
expect(result.confidence).toBeGreaterThan(0.5)
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it('should detect testing from Jest keywords', () => {
|
|
113
|
+
const result = smartContext.detectDomain('Write jest spec for validation')
|
|
114
|
+
expect(result.primary).toBe('testing')
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
it('should detect testing from e2e keywords', () => {
|
|
118
|
+
const result = smartContext.detectDomain('Add e2e integration tests')
|
|
119
|
+
expect(result.primary).toBe('testing')
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
it('should detect testing from coverage keywords', () => {
|
|
123
|
+
const result = smartContext.detectDomain('Improve test coverage for auth module')
|
|
124
|
+
expect(result.primary).toBe('testing')
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it('should detect testing from mock/fixture keywords', () => {
|
|
128
|
+
const result = smartContext.detectDomain('Create mock fixtures for API tests')
|
|
129
|
+
expect(result.primary).toBe('testing')
|
|
130
|
+
})
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
describe('general/fallback detection', () => {
|
|
134
|
+
it('should return general for ambiguous tasks', () => {
|
|
135
|
+
const result = smartContext.detectDomain('Refactor code')
|
|
136
|
+
expect(result.primary).toBe('general')
|
|
137
|
+
expect(result.confidence).toBe(0.5)
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
it('should return general for empty descriptions', () => {
|
|
141
|
+
const result = smartContext.detectDomain('')
|
|
142
|
+
expect(result.primary).toBe('general')
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
it('should return general for non-technical descriptions', () => {
|
|
146
|
+
// Note: "be" in "better" matches backend keyword, so use different text
|
|
147
|
+
const result = smartContext.detectDomain('Improve this somehow')
|
|
148
|
+
expect(result.primary).toBe('general')
|
|
149
|
+
})
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
describe('secondary domains', () => {
|
|
153
|
+
it('should detect secondary domains for mixed tasks', () => {
|
|
154
|
+
const result = smartContext.detectDomain('Add API endpoint with React frontend component')
|
|
155
|
+
expect(result.primary).toBeDefined()
|
|
156
|
+
expect(result.secondary.length).toBeGreaterThan(0)
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
it('should limit secondary domains to max 2', () => {
|
|
160
|
+
const result = smartContext.detectDomain(
|
|
161
|
+
'Add API endpoint with React component and Docker deploy with test coverage'
|
|
162
|
+
)
|
|
163
|
+
expect(result.secondary.length).toBeLessThanOrEqual(2)
|
|
164
|
+
})
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
describe('confidence scoring', () => {
|
|
168
|
+
it('should have higher confidence for strong domain signals', () => {
|
|
169
|
+
const strong = smartContext.detectDomain('Create React component with jsx tsx ui')
|
|
170
|
+
const weak = smartContext.detectDomain('Fix bug in component')
|
|
171
|
+
// Strong has multiple matching keywords, weak has fewer
|
|
172
|
+
expect(strong.confidence).toBeGreaterThanOrEqual(weak.confidence)
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
it('should cap confidence at 0.95', () => {
|
|
176
|
+
const result = smartContext.detectDomain(
|
|
177
|
+
'ui component react vue angular css style button form modal layout responsive animation dom html frontend jsx tsx'
|
|
178
|
+
)
|
|
179
|
+
expect(result.confidence).toBeLessThanOrEqual(0.95)
|
|
180
|
+
})
|
|
181
|
+
})
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
describe('filterFiles', () => {
|
|
185
|
+
const testFiles = [
|
|
186
|
+
'src/components/Button.tsx',
|
|
187
|
+
'src/components/Modal.jsx',
|
|
188
|
+
'src/api/users.ts',
|
|
189
|
+
'src/services/auth.ts',
|
|
190
|
+
'src/models/User.ts',
|
|
191
|
+
'docker/Dockerfile',
|
|
192
|
+
'.github/workflows/ci.yml',
|
|
193
|
+
'deploy/k8s/deployment.yaml',
|
|
194
|
+
'docs/README.md',
|
|
195
|
+
'docs/api.md',
|
|
196
|
+
'tests/unit/auth.test.ts',
|
|
197
|
+
'__tests__/components/Button.test.tsx',
|
|
198
|
+
'package.json',
|
|
199
|
+
'tsconfig.json',
|
|
200
|
+
'vite.config.ts',
|
|
201
|
+
]
|
|
202
|
+
|
|
203
|
+
// Access private method via class instance
|
|
204
|
+
const smartContextInstance = new SmartContext()
|
|
205
|
+
const filterFiles = (files: string[], domain: ContextDomain) => {
|
|
206
|
+
// @ts-expect-error - accessing private method for testing
|
|
207
|
+
return smartContextInstance.filterFiles(files, domain)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
describe('frontend filtering', () => {
|
|
211
|
+
it('should include component files', () => {
|
|
212
|
+
const result = filterFiles(testFiles, 'frontend')
|
|
213
|
+
expect(result).toContain('src/components/Button.tsx')
|
|
214
|
+
expect(result).toContain('src/components/Modal.jsx')
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
it('should include config files', () => {
|
|
218
|
+
const result = filterFiles(testFiles, 'frontend')
|
|
219
|
+
expect(result).toContain('package.json')
|
|
220
|
+
expect(result).toContain('tsconfig.json')
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
it('should exclude non-code files', () => {
|
|
224
|
+
const result = filterFiles(testFiles, 'frontend')
|
|
225
|
+
// Note: .ts files match frontend pattern /\.(tsx?|jsx?)$/ because tsx? = ts or tsx
|
|
226
|
+
// So backend .ts files are included - this is a known quirk
|
|
227
|
+
expect(result).not.toContain('docker/Dockerfile')
|
|
228
|
+
expect(result).not.toContain('docs/README.md')
|
|
229
|
+
})
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
describe('backend filtering', () => {
|
|
233
|
+
it('should include API and service files', () => {
|
|
234
|
+
const result = filterFiles(testFiles, 'backend')
|
|
235
|
+
expect(result).toContain('src/api/users.ts')
|
|
236
|
+
expect(result).toContain('src/services/auth.ts')
|
|
237
|
+
expect(result).toContain('src/models/User.ts')
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
it('should include config files', () => {
|
|
241
|
+
const result = filterFiles(testFiles, 'backend')
|
|
242
|
+
expect(result).toContain('package.json')
|
|
243
|
+
})
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
describe('devops filtering', () => {
|
|
247
|
+
it('should include Docker and CI files', () => {
|
|
248
|
+
const result = filterFiles(testFiles, 'devops')
|
|
249
|
+
expect(result).toContain('docker/Dockerfile')
|
|
250
|
+
expect(result).toContain('.github/workflows/ci.yml')
|
|
251
|
+
expect(result).toContain('deploy/k8s/deployment.yaml')
|
|
252
|
+
})
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
describe('docs filtering', () => {
|
|
256
|
+
it('should include markdown files', () => {
|
|
257
|
+
const result = filterFiles(testFiles, 'docs')
|
|
258
|
+
expect(result).toContain('docs/README.md')
|
|
259
|
+
expect(result).toContain('docs/api.md')
|
|
260
|
+
})
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
describe('testing filtering', () => {
|
|
264
|
+
it('should include test files', () => {
|
|
265
|
+
const result = filterFiles(testFiles, 'testing')
|
|
266
|
+
expect(result).toContain('tests/unit/auth.test.ts')
|
|
267
|
+
expect(result).toContain('__tests__/components/Button.test.tsx')
|
|
268
|
+
})
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
describe('general filtering', () => {
|
|
272
|
+
it('should return all files for general domain', () => {
|
|
273
|
+
const result = filterFiles(testFiles, 'general')
|
|
274
|
+
expect(result).toEqual(testFiles)
|
|
275
|
+
})
|
|
276
|
+
})
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
describe('estimateSize', () => {
|
|
280
|
+
// @ts-expect-error - accessing private method for testing
|
|
281
|
+
const estimateSize = smartContext.estimateSize.bind(smartContext)
|
|
282
|
+
|
|
283
|
+
it('should return minimum 100 for empty context', () => {
|
|
284
|
+
const result = estimateSize({})
|
|
285
|
+
expect(result).toBe(100)
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
it('should estimate agents at ~50 tokens each', () => {
|
|
289
|
+
const result = estimateSize({ agents: [{}, {}, {}] })
|
|
290
|
+
expect(result).toBe(150) // 3 * 50
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
it('should estimate roadmap items at ~50 tokens each', () => {
|
|
294
|
+
const result = estimateSize({ roadmap: [{}, {}] })
|
|
295
|
+
expect(result).toBe(100) // 2 * 50
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
it('should estimate patterns at ~30 tokens each', () => {
|
|
299
|
+
const result = estimateSize({ patterns: [{}, {}, {}, {}] })
|
|
300
|
+
expect(result).toBe(120) // 4 * 30
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
it('should estimate stack at 100 tokens', () => {
|
|
304
|
+
const result = estimateSize({ stack: {} })
|
|
305
|
+
expect(result).toBe(100)
|
|
306
|
+
})
|
|
307
|
+
|
|
308
|
+
it('should estimate files at ~10 tokens each', () => {
|
|
309
|
+
const result = estimateSize({ files: ['a', 'b', 'c', 'd', 'e'] })
|
|
310
|
+
expect(result).toBe(100) // 5 * 10 = 50, min 100
|
|
311
|
+
})
|
|
312
|
+
|
|
313
|
+
it('should estimate state at 200 tokens', () => {
|
|
314
|
+
const result = estimateSize({ state: {} })
|
|
315
|
+
expect(result).toBe(200)
|
|
316
|
+
})
|
|
317
|
+
|
|
318
|
+
it('should combine all estimates', () => {
|
|
319
|
+
const result = estimateSize({
|
|
320
|
+
agents: [{}, {}], // 100
|
|
321
|
+
roadmap: [{}], // 50
|
|
322
|
+
patterns: [{}], // 30
|
|
323
|
+
stack: {}, // 100
|
|
324
|
+
files: ['a'], // 10
|
|
325
|
+
state: {}, // 200
|
|
326
|
+
})
|
|
327
|
+
expect(result).toBe(490)
|
|
328
|
+
})
|
|
329
|
+
})
|
|
330
|
+
|
|
331
|
+
describe('taskTypeToContextDomain', () => {
|
|
332
|
+
const testCases: Array<{ input: TaskType; expected: ContextDomain }> = [
|
|
333
|
+
{ input: 'frontend', expected: 'frontend' },
|
|
334
|
+
{ input: 'backend', expected: 'backend' },
|
|
335
|
+
{ input: 'devops', expected: 'devops' },
|
|
336
|
+
{ input: 'database', expected: 'backend' },
|
|
337
|
+
{ input: 'testing', expected: 'testing' },
|
|
338
|
+
{ input: 'documentation', expected: 'docs' },
|
|
339
|
+
{ input: 'refactoring', expected: 'general' },
|
|
340
|
+
{ input: 'bugfix', expected: 'general' },
|
|
341
|
+
{ input: 'feature', expected: 'general' },
|
|
342
|
+
{ input: 'design', expected: 'frontend' },
|
|
343
|
+
{ input: 'other', expected: 'general' },
|
|
344
|
+
]
|
|
345
|
+
|
|
346
|
+
for (const { input, expected } of testCases) {
|
|
347
|
+
it(`should map ${input} to ${expected}`, () => {
|
|
348
|
+
expect(smartContext.taskTypeToContextDomain(input)).toBe(expected)
|
|
349
|
+
})
|
|
350
|
+
}
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
describe('contextDomainToTaskType (via getRecommendedContext)', () => {
|
|
354
|
+
// Test the private method indirectly through detectDomain verification
|
|
355
|
+
it('should have consistent bidirectional mapping for core domains', () => {
|
|
356
|
+
// Frontend maps to frontend
|
|
357
|
+
const frontendResult = smartContext.detectDomain('Create React component')
|
|
358
|
+
expect(frontendResult.primary).toBe('frontend')
|
|
359
|
+
expect(smartContext.taskTypeToContextDomain('frontend')).toBe('frontend')
|
|
360
|
+
|
|
361
|
+
// Backend maps to backend
|
|
362
|
+
const backendResult = smartContext.detectDomain('Add API endpoint')
|
|
363
|
+
expect(backendResult.primary).toBe('backend')
|
|
364
|
+
expect(smartContext.taskTypeToContextDomain('backend')).toBe('backend')
|
|
365
|
+
|
|
366
|
+
// Testing maps to testing
|
|
367
|
+
const testingResult = smartContext.detectDomain('Add unit test')
|
|
368
|
+
expect(testingResult.primary).toBe('testing')
|
|
369
|
+
expect(smartContext.taskTypeToContextDomain('testing')).toBe('testing')
|
|
370
|
+
})
|
|
371
|
+
})
|
|
372
|
+
})
|
package/core/constants/index.ts
CHANGED
|
@@ -218,6 +218,52 @@ export const PLANNING_TOOLS = [
|
|
|
218
218
|
'GetDateTime',
|
|
219
219
|
] as const
|
|
220
220
|
|
|
221
|
+
// =============================================================================
|
|
222
|
+
// Timeout Constants (PRJ-111)
|
|
223
|
+
// =============================================================================
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Timeout values in milliseconds for various operations.
|
|
227
|
+
* Can be overridden via PRJCT_TIMEOUT_* environment variables.
|
|
228
|
+
*/
|
|
229
|
+
export const TIMEOUTS = {
|
|
230
|
+
/** Tool availability checks (git --version, npm --version) */
|
|
231
|
+
TOOL_CHECK: 5_000,
|
|
232
|
+
|
|
233
|
+
/** Standard git operations (status, add, commit) */
|
|
234
|
+
GIT_OPERATION: 10_000,
|
|
235
|
+
|
|
236
|
+
/** Git clone with --depth 1 */
|
|
237
|
+
GIT_CLONE: 60_000,
|
|
238
|
+
|
|
239
|
+
/** HTTP fetch/API requests */
|
|
240
|
+
API_REQUEST: 30_000,
|
|
241
|
+
|
|
242
|
+
/** npm install -g (CLI installation) - 2 minutes */
|
|
243
|
+
NPM_INSTALL: 120_000,
|
|
244
|
+
|
|
245
|
+
/** User-defined workflow hooks */
|
|
246
|
+
WORKFLOW_HOOK: 60_000,
|
|
247
|
+
} as const
|
|
248
|
+
|
|
249
|
+
export type TimeoutKey = keyof typeof TIMEOUTS
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Get timeout value with optional environment variable override.
|
|
253
|
+
* Environment variables: PRJCT_TIMEOUT_TOOL_CHECK, PRJCT_TIMEOUT_GIT_OPERATION, etc.
|
|
254
|
+
*/
|
|
255
|
+
export function getTimeout(key: TimeoutKey): number {
|
|
256
|
+
const envVar = `PRJCT_TIMEOUT_${key}`
|
|
257
|
+
const envValue = process.env[envVar]
|
|
258
|
+
if (envValue) {
|
|
259
|
+
const parsed = Number.parseInt(envValue, 10)
|
|
260
|
+
if (!Number.isNaN(parsed) && parsed > 0) {
|
|
261
|
+
return parsed
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return TIMEOUTS[key]
|
|
265
|
+
}
|
|
266
|
+
|
|
221
267
|
// =============================================================================
|
|
222
268
|
// Combined Exports
|
|
223
269
|
// =============================================================================
|
|
@@ -245,3 +291,11 @@ export const PLAN = {
|
|
|
245
291
|
DESTRUCTIVE_COMMANDS,
|
|
246
292
|
TOOLS: PLANNING_TOOLS,
|
|
247
293
|
} as const
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Combined timeout exports for easy import.
|
|
297
|
+
*/
|
|
298
|
+
export const TIMEOUT = {
|
|
299
|
+
VALUES: TIMEOUTS,
|
|
300
|
+
get: getTimeout,
|
|
301
|
+
} as const
|
|
@@ -21,6 +21,7 @@ import { execSync } from 'node:child_process'
|
|
|
21
21
|
import fs from 'node:fs'
|
|
22
22
|
import os from 'node:os'
|
|
23
23
|
import path from 'node:path'
|
|
24
|
+
import { getTimeout } from '../constants'
|
|
24
25
|
import { dependencyValidator } from '../services/dependency-validator'
|
|
25
26
|
import { isNotFoundError } from '../types/fs'
|
|
26
27
|
import type { AIProviderConfig, AIProviderName } from '../types/provider'
|
|
@@ -91,15 +92,28 @@ async function installAICLI(provider: AIProviderConfig): Promise<boolean> {
|
|
|
91
92
|
try {
|
|
92
93
|
console.log(`${YELLOW}📦 ${provider.displayName} not found. Installing...${NC}`)
|
|
93
94
|
console.log('')
|
|
94
|
-
|
|
95
|
+
// PRJ-111: Add timeout to npm install (default: 2 minutes, configurable via PRJCT_TIMEOUT_NPM_INSTALL)
|
|
96
|
+
execSync(`npm install -g ${packageName}`, {
|
|
97
|
+
stdio: 'inherit',
|
|
98
|
+
timeout: getTimeout('NPM_INSTALL'),
|
|
99
|
+
})
|
|
95
100
|
console.log('')
|
|
96
101
|
console.log(`${GREEN}✓${NC} ${provider.displayName} installed successfully`)
|
|
97
102
|
console.log('')
|
|
98
103
|
return true
|
|
99
104
|
} catch (error) {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
105
|
+
const err = error as Error & { killed?: boolean; signal?: string }
|
|
106
|
+
const isTimeout = err.killed && err.signal === 'SIGTERM'
|
|
107
|
+
|
|
108
|
+
if (isTimeout) {
|
|
109
|
+
console.log(`${YELLOW}⚠️ Installation timed out for ${provider.displayName}${NC}`)
|
|
110
|
+
console.log('')
|
|
111
|
+
console.log(`${DIM}The npm install took too long. Try:${NC}`)
|
|
112
|
+
console.log(`${DIM} • Set PRJCT_TIMEOUT_NPM_INSTALL=300000 for 5 minutes${NC}`)
|
|
113
|
+
console.log(`${DIM} • Run manually: npm install -g ${packageName}${NC}`)
|
|
114
|
+
} else {
|
|
115
|
+
console.log(`${YELLOW}⚠️ Failed to install ${provider.displayName}: ${err.message}${NC}`)
|
|
116
|
+
}
|
|
103
117
|
console.log('')
|
|
104
118
|
console.log(`${DIM}Alternative installation methods:${NC}`)
|
|
105
119
|
console.log(`${DIM} • npm: npm install -g ${packageName}${NC}`)
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import { exec } from 'node:child_process'
|
|
14
14
|
import { promisify } from 'node:util'
|
|
15
|
+
import { getTimeout } from '../constants'
|
|
15
16
|
import { dependencyValidator } from './dependency-validator'
|
|
16
17
|
|
|
17
18
|
const execAsync = promisify(exec)
|
|
@@ -99,6 +100,7 @@ export class GitAnalyzer {
|
|
|
99
100
|
try {
|
|
100
101
|
const { stdout } = await execAsync('git branch --show-current', {
|
|
101
102
|
cwd: this.projectPath,
|
|
103
|
+
timeout: getTimeout('GIT_OPERATION'),
|
|
102
104
|
})
|
|
103
105
|
return stdout.trim() || 'main'
|
|
104
106
|
} catch {
|
|
@@ -113,6 +115,7 @@ export class GitAnalyzer {
|
|
|
113
115
|
try {
|
|
114
116
|
const { stdout } = await execAsync('git rev-list --count HEAD', {
|
|
115
117
|
cwd: this.projectPath,
|
|
118
|
+
timeout: getTimeout('GIT_OPERATION'),
|
|
116
119
|
})
|
|
117
120
|
return parseInt(stdout.trim(), 10) || 0
|
|
118
121
|
} catch {
|
|
@@ -128,6 +131,7 @@ export class GitAnalyzer {
|
|
|
128
131
|
try {
|
|
129
132
|
const { stdout } = await execAsync('git shortlog -sn --all', {
|
|
130
133
|
cwd: this.projectPath,
|
|
134
|
+
timeout: getTimeout('GIT_OPERATION'),
|
|
131
135
|
})
|
|
132
136
|
// Count non-empty lines instead of piping to wc -l
|
|
133
137
|
const lines = stdout.trim().split('\n').filter(Boolean)
|
|
@@ -156,6 +160,7 @@ export class GitAnalyzer {
|
|
|
156
160
|
try {
|
|
157
161
|
const { stdout } = await execAsync('git status --porcelain', {
|
|
158
162
|
cwd: this.projectPath,
|
|
163
|
+
timeout: getTimeout('GIT_OPERATION'),
|
|
159
164
|
})
|
|
160
165
|
const lines = stdout.trim().split('\n').filter(Boolean)
|
|
161
166
|
result.hasChanges = lines.length > 0
|
|
@@ -188,7 +193,7 @@ export class GitAnalyzer {
|
|
|
188
193
|
try {
|
|
189
194
|
const { stdout } = await execAsync(
|
|
190
195
|
`git log --oneline -${count} --pretty=format:"%h|%s|%ad" --date=short`,
|
|
191
|
-
{ cwd: this.projectPath }
|
|
196
|
+
{ cwd: this.projectPath, timeout: getTimeout('GIT_OPERATION') }
|
|
192
197
|
)
|
|
193
198
|
|
|
194
199
|
return stdout
|
|
@@ -211,6 +216,7 @@ export class GitAnalyzer {
|
|
|
211
216
|
try {
|
|
212
217
|
const { stdout } = await execAsync('git log --oneline --since="1 week ago"', {
|
|
213
218
|
cwd: this.projectPath,
|
|
219
|
+
timeout: getTimeout('GIT_OPERATION'),
|
|
214
220
|
})
|
|
215
221
|
// Count non-empty lines instead of piping to wc -l
|
|
216
222
|
const lines = stdout.trim().split('\n').filter(Boolean)
|
|
@@ -227,6 +233,7 @@ export class GitAnalyzer {
|
|
|
227
233
|
try {
|
|
228
234
|
await execAsync('git rev-parse --is-inside-work-tree', {
|
|
229
235
|
cwd: this.projectPath,
|
|
236
|
+
timeout: getTimeout('GIT_OPERATION'),
|
|
230
237
|
})
|
|
231
238
|
return true
|
|
232
239
|
} catch {
|
|
@@ -243,6 +250,7 @@ export class GitAnalyzer {
|
|
|
243
250
|
// Try to get from remote
|
|
244
251
|
const { stdout } = await execAsync('git symbolic-ref refs/remotes/origin/HEAD', {
|
|
245
252
|
cwd: this.projectPath,
|
|
253
|
+
timeout: getTimeout('GIT_OPERATION'),
|
|
246
254
|
})
|
|
247
255
|
// Use JS replace instead of piping to sed
|
|
248
256
|
const branch = stdout.trim().replace(/^refs\/remotes\/origin\//, '')
|
|
@@ -255,6 +263,7 @@ export class GitAnalyzer {
|
|
|
255
263
|
try {
|
|
256
264
|
await execAsync('git show-ref --verify --quiet refs/heads/main', {
|
|
257
265
|
cwd: this.projectPath,
|
|
266
|
+
timeout: getTimeout('GIT_OPERATION'),
|
|
258
267
|
})
|
|
259
268
|
return 'main'
|
|
260
269
|
} catch {
|
|
@@ -20,6 +20,7 @@ import os from 'node:os'
|
|
|
20
20
|
import path from 'node:path'
|
|
21
21
|
import { promisify } from 'node:util'
|
|
22
22
|
import { glob } from 'glob'
|
|
23
|
+
import { getTimeout } from '../constants'
|
|
23
24
|
import { dependencyValidator } from './dependency-validator'
|
|
24
25
|
import type { SkillLockEntry } from './skill-lock'
|
|
25
26
|
import { skillLock } from './skill-lock'
|
|
@@ -252,13 +253,17 @@ async function installFromGitHub(source: ParsedSource): Promise<InstallResult> {
|
|
|
252
253
|
|
|
253
254
|
try {
|
|
254
255
|
// Clone with depth 1 for speed
|
|
256
|
+
// PRJ-111: Configurable timeout (default: 60s, override via PRJCT_TIMEOUT_GIT_CLONE)
|
|
255
257
|
const cloneUrl = `https://github.com/${source.owner}/${source.repo}.git`
|
|
256
|
-
await exec(`git clone --depth 1 ${cloneUrl} ${tmpDir}`, { timeout:
|
|
258
|
+
await exec(`git clone --depth 1 ${cloneUrl} ${tmpDir}`, { timeout: getTimeout('GIT_CLONE') })
|
|
257
259
|
|
|
258
260
|
// Get the commit SHA
|
|
259
261
|
let sha: string | undefined
|
|
260
262
|
try {
|
|
261
|
-
const { stdout } = await exec('git rev-parse HEAD', {
|
|
263
|
+
const { stdout } = await exec('git rev-parse HEAD', {
|
|
264
|
+
cwd: tmpDir,
|
|
265
|
+
timeout: getTimeout('TOOL_CHECK'),
|
|
266
|
+
})
|
|
262
267
|
sha = stdout.trim()
|
|
263
268
|
} catch {
|
|
264
269
|
// Non-critical
|
package/core/sync/sync-client.ts
CHANGED
|
@@ -3,8 +3,11 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Handles communication with the backend API for push/pull operations.
|
|
5
5
|
* Uses native fetch API (available in Node 18+ and Bun).
|
|
6
|
+
*
|
|
7
|
+
* PRJ-111: Includes configurable timeout support via AbortController.
|
|
6
8
|
*/
|
|
7
9
|
|
|
10
|
+
import { getTimeout } from '../constants'
|
|
8
11
|
import type { SyncEvent } from '../events'
|
|
9
12
|
import type { SyncBatchResult, SyncClientError, SyncPullResult, SyncStatus } from '../types'
|
|
10
13
|
import authConfig from './auth-config'
|
|
@@ -114,10 +117,15 @@ class SyncClient {
|
|
|
114
117
|
* Test connection to the API
|
|
115
118
|
*/
|
|
116
119
|
async testConnection(): Promise<boolean> {
|
|
120
|
+
// PRJ-111: Add timeout to connection test
|
|
121
|
+
const controller = new AbortController()
|
|
122
|
+
const timeoutId = setTimeout(() => controller.abort(), getTimeout('API_REQUEST'))
|
|
123
|
+
|
|
117
124
|
try {
|
|
118
125
|
const { apiUrl, apiKey } = await this.getAuthHeaders()
|
|
119
126
|
|
|
120
127
|
if (!apiKey) {
|
|
128
|
+
clearTimeout(timeoutId)
|
|
121
129
|
return false
|
|
122
130
|
}
|
|
123
131
|
|
|
@@ -126,11 +134,14 @@ class SyncClient {
|
|
|
126
134
|
headers: {
|
|
127
135
|
'X-Api-Key': apiKey,
|
|
128
136
|
},
|
|
137
|
+
signal: controller.signal,
|
|
129
138
|
})
|
|
130
139
|
|
|
140
|
+
clearTimeout(timeoutId)
|
|
131
141
|
return response.ok
|
|
132
142
|
} catch (_error) {
|
|
133
|
-
// Network error or other issue - expected
|
|
143
|
+
// Network error, timeout, or other issue - expected
|
|
144
|
+
clearTimeout(timeoutId)
|
|
134
145
|
return false
|
|
135
146
|
}
|
|
136
147
|
}
|
|
@@ -156,8 +167,17 @@ class SyncClient {
|
|
|
156
167
|
options: RequestInit,
|
|
157
168
|
retryCount = 0
|
|
158
169
|
): Promise<Response> {
|
|
170
|
+
// PRJ-111: Add AbortController-based timeout (default: 30s, configurable via PRJCT_TIMEOUT_API_REQUEST)
|
|
171
|
+
const controller = new AbortController()
|
|
172
|
+
const timeoutId = setTimeout(() => controller.abort(), getTimeout('API_REQUEST'))
|
|
173
|
+
|
|
159
174
|
try {
|
|
160
|
-
const response = await fetch(url,
|
|
175
|
+
const response = await fetch(url, {
|
|
176
|
+
...options,
|
|
177
|
+
signal: controller.signal,
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
clearTimeout(timeoutId)
|
|
161
181
|
|
|
162
182
|
// Retry on server errors (5xx) but not client errors (4xx)
|
|
163
183
|
if (response.status >= 500 && retryCount < this.retryConfig.maxRetries) {
|
|
@@ -171,6 +191,16 @@ class SyncClient {
|
|
|
171
191
|
|
|
172
192
|
return response
|
|
173
193
|
} catch (error) {
|
|
194
|
+
clearTimeout(timeoutId)
|
|
195
|
+
|
|
196
|
+
// Check if this is a timeout (AbortError)
|
|
197
|
+
if (error instanceof Error && error.name === 'AbortError') {
|
|
198
|
+
throw this.createError(
|
|
199
|
+
'NETWORK_ERROR',
|
|
200
|
+
`Request timed out. Try increasing PRJCT_TIMEOUT_API_REQUEST (current: ${getTimeout('API_REQUEST')}ms)`
|
|
201
|
+
)
|
|
202
|
+
}
|
|
203
|
+
|
|
174
204
|
// Retry on network errors
|
|
175
205
|
if (retryCount < this.retryConfig.maxRetries) {
|
|
176
206
|
const delay = Math.min(
|
package/core/types/agentic.ts
CHANGED
|
@@ -5,6 +5,66 @@
|
|
|
5
5
|
|
|
6
6
|
import type { CommandParams, ContextPaths } from './core'
|
|
7
7
|
|
|
8
|
+
// =============================================================================
|
|
9
|
+
// Input Source Types (PRJ-102)
|
|
10
|
+
// =============================================================================
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Source of context data - tracks origin for traceability.
|
|
14
|
+
* Pattern from Anthropic: always track where data comes from.
|
|
15
|
+
*
|
|
16
|
+
* @see PRJ-102
|
|
17
|
+
*/
|
|
18
|
+
export type InputSource =
|
|
19
|
+
| 'user_explicit' // User directly provided this (e.g., task description, idea)
|
|
20
|
+
| 'user_file' // From a user-created file (e.g., PRJCT.md, custom agents)
|
|
21
|
+
| 'system_detected' // Automatically detected (e.g., stack detection, git analysis)
|
|
22
|
+
| 'system_generated' // Generated by the system (e.g., now.md, analysis)
|
|
23
|
+
| 'system_inferred' // Inferred from other data (e.g., domain detection)
|
|
24
|
+
| 'learned' // Learned from patterns/history
|
|
25
|
+
| 'external' // From external service (e.g., Linear, JIRA)
|
|
26
|
+
| 'inherited' // Inherited from parent context (monorepo)
|
|
27
|
+
| 'cached' // From cache (may be stale)
|
|
28
|
+
| 'unknown' // Source not tracked (legacy data)
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Metadata about data source and freshness.
|
|
32
|
+
*/
|
|
33
|
+
export interface SourceMetadata {
|
|
34
|
+
/** Where this data came from */
|
|
35
|
+
source: InputSource
|
|
36
|
+
/** When this was captured/detected */
|
|
37
|
+
capturedAt: string
|
|
38
|
+
/** Confidence in this data (0-1) */
|
|
39
|
+
confidence?: number
|
|
40
|
+
/** Path to source file if applicable */
|
|
41
|
+
sourcePath?: string
|
|
42
|
+
/** Whether this might be stale */
|
|
43
|
+
mayBeStale?: boolean
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Base interface for any item with source tracking.
|
|
48
|
+
*/
|
|
49
|
+
export interface SourcedItem {
|
|
50
|
+
/** Metadata about where this item came from */
|
|
51
|
+
_source?: SourceMetadata
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Create source metadata with current timestamp.
|
|
56
|
+
*/
|
|
57
|
+
export function createSourceMetadata(
|
|
58
|
+
source: InputSource,
|
|
59
|
+
options: Partial<Omit<SourceMetadata, 'source'>> = {}
|
|
60
|
+
): SourceMetadata {
|
|
61
|
+
return {
|
|
62
|
+
source,
|
|
63
|
+
capturedAt: new Date().toISOString(),
|
|
64
|
+
...options,
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
8
68
|
// =============================================================================
|
|
9
69
|
// Tool Registry Types
|
|
10
70
|
// =============================================================================
|
|
@@ -73,21 +133,21 @@ export interface FilteredContext {
|
|
|
73
133
|
metrics: FilterMetrics
|
|
74
134
|
}
|
|
75
135
|
|
|
76
|
-
export interface AgentInfo {
|
|
136
|
+
export interface AgentInfo extends SourcedItem {
|
|
77
137
|
name: string
|
|
78
138
|
domain: ContextDomain
|
|
79
139
|
skills: string[]
|
|
80
140
|
successRate?: number
|
|
81
141
|
}
|
|
82
142
|
|
|
83
|
-
export interface FeatureInfo {
|
|
143
|
+
export interface FeatureInfo extends SourcedItem {
|
|
84
144
|
id: string
|
|
85
145
|
name: string
|
|
86
146
|
relatedTo: ContextDomain[]
|
|
87
147
|
status: string
|
|
88
148
|
}
|
|
89
149
|
|
|
90
|
-
export interface PatternInfo {
|
|
150
|
+
export interface PatternInfo extends SourcedItem {
|
|
91
151
|
description: string
|
|
92
152
|
domain: ContextDomain
|
|
93
153
|
confidence: number
|
|
@@ -211,11 +271,14 @@ export interface PlanParams extends CommandParams {
|
|
|
211
271
|
verbose?: boolean
|
|
212
272
|
}
|
|
213
273
|
|
|
214
|
-
export interface GatheredInfo {
|
|
274
|
+
export interface GatheredInfo extends SourcedItem {
|
|
215
275
|
type: GatheredInfoType
|
|
276
|
+
/** Path or identifier of the source */
|
|
216
277
|
source: string
|
|
217
278
|
data: string | GatheredFileData | GatheredAnalysisData
|
|
218
279
|
gatheredAt: string
|
|
280
|
+
/** Input source category for traceability */
|
|
281
|
+
inputSource?: InputSource
|
|
219
282
|
}
|
|
220
283
|
|
|
221
284
|
export type GatheredInfoType =
|
|
@@ -580,7 +643,7 @@ export interface LearnedPatterns {
|
|
|
580
643
|
/**
|
|
581
644
|
* A loaded agent with its content
|
|
582
645
|
*/
|
|
583
|
-
export interface LoadedAgent {
|
|
646
|
+
export interface LoadedAgent extends SourcedItem {
|
|
584
647
|
name: string
|
|
585
648
|
domain: string
|
|
586
649
|
content: string
|
package/core/types/index.ts
CHANGED
|
@@ -50,6 +50,8 @@ export type {
|
|
|
50
50
|
GroundTruthContext,
|
|
51
51
|
HallucinationPattern,
|
|
52
52
|
HallucinationResult,
|
|
53
|
+
// Input Source types (PRJ-102)
|
|
54
|
+
InputSource,
|
|
53
55
|
LearnedPatterns,
|
|
54
56
|
LearnedPatternsRecord,
|
|
55
57
|
LoadedAgent,
|
|
@@ -81,6 +83,9 @@ export type {
|
|
|
81
83
|
SimpleExecutionResult,
|
|
82
84
|
SkillContext,
|
|
83
85
|
SmartContextProjectState,
|
|
86
|
+
// Source tracking types (PRJ-102)
|
|
87
|
+
SourcedItem,
|
|
88
|
+
SourceMetadata,
|
|
84
89
|
StackInfo,
|
|
85
90
|
Template,
|
|
86
91
|
ThinkBlock,
|
|
@@ -92,6 +97,7 @@ export type {
|
|
|
92
97
|
VerificationResult,
|
|
93
98
|
Verifier,
|
|
94
99
|
} from './agentic'
|
|
100
|
+
export { createSourceMetadata } from './agentic'
|
|
95
101
|
// =============================================================================
|
|
96
102
|
// Agent Types
|
|
97
103
|
// =============================================================================
|
|
@@ -248,6 +254,7 @@ export type {
|
|
|
248
254
|
Decision,
|
|
249
255
|
HistoryEntry,
|
|
250
256
|
HistoryEventType,
|
|
257
|
+
LegacyMemorySource,
|
|
251
258
|
Memory,
|
|
252
259
|
MemoryContext,
|
|
253
260
|
MemoryContextParams,
|
package/core/types/memory.ts
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* Types for the memory system that tracks user preferences and patterns.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import type { InputSource } from './agentic'
|
|
7
|
+
|
|
6
8
|
/**
|
|
7
9
|
* Semantic tags for memory categorization.
|
|
8
10
|
* Use these constants instead of raw strings.
|
|
@@ -49,6 +51,12 @@ export interface Memory {
|
|
|
49
51
|
observationCount?: number
|
|
50
52
|
}
|
|
51
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Legacy source type - use InputSource for new code.
|
|
56
|
+
* @deprecated Use InputSource from agentic.ts instead
|
|
57
|
+
*/
|
|
58
|
+
export type LegacyMemorySource = 'user' | 'system' | 'learned'
|
|
59
|
+
|
|
52
60
|
/**
|
|
53
61
|
* Metadata associated with a memory entry.
|
|
54
62
|
*/
|
|
@@ -61,8 +69,10 @@ export interface MemoryMetadata {
|
|
|
61
69
|
taskId?: string
|
|
62
70
|
/** Related feature name */
|
|
63
71
|
feature?: string
|
|
64
|
-
/** Source of the memory */
|
|
65
|
-
source?:
|
|
72
|
+
/** Source of the memory (legacy - use inputSource for new code) */
|
|
73
|
+
source?: LegacyMemorySource
|
|
74
|
+
/** Input source category for traceability (PRJ-102) */
|
|
75
|
+
inputSource?: InputSource
|
|
66
76
|
}
|
|
67
77
|
|
|
68
78
|
/**
|
package/dist/bin/prjct.mjs
CHANGED
|
@@ -11952,7 +11952,18 @@ var init_orchestrator_executor = __esm({
|
|
|
11952
11952
|
});
|
|
11953
11953
|
|
|
11954
11954
|
// core/constants/index.ts
|
|
11955
|
-
|
|
11955
|
+
function getTimeout(key) {
|
|
11956
|
+
const envVar = `PRJCT_TIMEOUT_${key}`;
|
|
11957
|
+
const envValue = process.env[envVar];
|
|
11958
|
+
if (envValue) {
|
|
11959
|
+
const parsed = Number.parseInt(envValue, 10);
|
|
11960
|
+
if (!Number.isNaN(parsed) && parsed > 0) {
|
|
11961
|
+
return parsed;
|
|
11962
|
+
}
|
|
11963
|
+
}
|
|
11964
|
+
return TIMEOUTS[key];
|
|
11965
|
+
}
|
|
11966
|
+
var PLAN_STATUS, PLAN_REQUIRED_COMMANDS, DESTRUCTIVE_COMMANDS, PLANNING_TOOLS, TIMEOUTS;
|
|
11956
11967
|
var init_constants = __esm({
|
|
11957
11968
|
"core/constants/index.ts"() {
|
|
11958
11969
|
"use strict";
|
|
@@ -11997,6 +12008,21 @@ var init_constants = __esm({
|
|
|
11997
12008
|
"GetDate",
|
|
11998
12009
|
"GetDateTime"
|
|
11999
12010
|
];
|
|
12011
|
+
TIMEOUTS = {
|
|
12012
|
+
/** Tool availability checks (git --version, npm --version) */
|
|
12013
|
+
TOOL_CHECK: 5e3,
|
|
12014
|
+
/** Standard git operations (status, add, commit) */
|
|
12015
|
+
GIT_OPERATION: 1e4,
|
|
12016
|
+
/** Git clone with --depth 1 */
|
|
12017
|
+
GIT_CLONE: 6e4,
|
|
12018
|
+
/** HTTP fetch/API requests */
|
|
12019
|
+
API_REQUEST: 3e4,
|
|
12020
|
+
/** npm install -g (CLI installation) - 2 minutes */
|
|
12021
|
+
NPM_INSTALL: 12e4,
|
|
12022
|
+
/** User-defined workflow hooks */
|
|
12023
|
+
WORKFLOW_HOOK: 6e4
|
|
12024
|
+
};
|
|
12025
|
+
__name(getTimeout, "getTimeout");
|
|
12000
12026
|
}
|
|
12001
12027
|
});
|
|
12002
12028
|
|
|
@@ -12393,6 +12419,13 @@ var init_plan_mode = __esm({
|
|
|
12393
12419
|
}
|
|
12394
12420
|
});
|
|
12395
12421
|
|
|
12422
|
+
// core/types/agentic.ts
|
|
12423
|
+
var init_agentic = __esm({
|
|
12424
|
+
"core/types/agentic.ts"() {
|
|
12425
|
+
"use strict";
|
|
12426
|
+
}
|
|
12427
|
+
});
|
|
12428
|
+
|
|
12396
12429
|
// core/types/bus.ts
|
|
12397
12430
|
var init_bus = __esm({
|
|
12398
12431
|
"core/types/bus.ts"() {
|
|
@@ -12419,6 +12452,7 @@ var init_integrations = __esm({
|
|
|
12419
12452
|
var init_types3 = __esm({
|
|
12420
12453
|
"core/types/index.ts"() {
|
|
12421
12454
|
"use strict";
|
|
12455
|
+
init_agentic();
|
|
12422
12456
|
init_bus();
|
|
12423
12457
|
init_fs();
|
|
12424
12458
|
init_integrations();
|
|
@@ -15844,6 +15878,7 @@ var execAsync2;
|
|
|
15844
15878
|
var init_git_analyzer = __esm({
|
|
15845
15879
|
"core/services/git-analyzer.ts"() {
|
|
15846
15880
|
"use strict";
|
|
15881
|
+
init_constants();
|
|
15847
15882
|
init_dependency_validator();
|
|
15848
15883
|
execAsync2 = promisify6(exec6);
|
|
15849
15884
|
}
|
|
@@ -22747,15 +22782,26 @@ async function installAICLI(provider) {
|
|
|
22747
22782
|
try {
|
|
22748
22783
|
console.log(`${YELLOW4}\u{1F4E6} ${provider.displayName} not found. Installing...${NC}`);
|
|
22749
22784
|
console.log("");
|
|
22750
|
-
execSync7(`npm install -g ${packageName}`, {
|
|
22785
|
+
execSync7(`npm install -g ${packageName}`, {
|
|
22786
|
+
stdio: "inherit",
|
|
22787
|
+
timeout: getTimeout("NPM_INSTALL")
|
|
22788
|
+
});
|
|
22751
22789
|
console.log("");
|
|
22752
22790
|
console.log(`${GREEN4}\u2713${NC} ${provider.displayName} installed successfully`);
|
|
22753
22791
|
console.log("");
|
|
22754
22792
|
return true;
|
|
22755
22793
|
} catch (error) {
|
|
22756
|
-
|
|
22757
|
-
|
|
22758
|
-
)
|
|
22794
|
+
const err = error;
|
|
22795
|
+
const isTimeout = err.killed && err.signal === "SIGTERM";
|
|
22796
|
+
if (isTimeout) {
|
|
22797
|
+
console.log(`${YELLOW4}\u26A0\uFE0F Installation timed out for ${provider.displayName}${NC}`);
|
|
22798
|
+
console.log("");
|
|
22799
|
+
console.log(`${DIM4}The npm install took too long. Try:${NC}`);
|
|
22800
|
+
console.log(`${DIM4} \u2022 Set PRJCT_TIMEOUT_NPM_INSTALL=300000 for 5 minutes${NC}`);
|
|
22801
|
+
console.log(`${DIM4} \u2022 Run manually: npm install -g ${packageName}${NC}`);
|
|
22802
|
+
} else {
|
|
22803
|
+
console.log(`${YELLOW4}\u26A0\uFE0F Failed to install ${provider.displayName}: ${err.message}${NC}`);
|
|
22804
|
+
}
|
|
22759
22805
|
console.log("");
|
|
22760
22806
|
console.log(`${DIM4}Alternative installation methods:${NC}`);
|
|
22761
22807
|
console.log(`${DIM4} \u2022 npm: npm install -g ${packageName}${NC}`);
|
|
@@ -23374,6 +23420,7 @@ var GREEN4, YELLOW4, DIM4, NC, setup_default, isDirectRun;
|
|
|
23374
23420
|
var init_setup = __esm({
|
|
23375
23421
|
"core/infrastructure/setup.ts"() {
|
|
23376
23422
|
"use strict";
|
|
23423
|
+
init_constants();
|
|
23377
23424
|
init_dependency_validator();
|
|
23378
23425
|
init_fs();
|
|
23379
23426
|
init_version();
|
|
@@ -26798,7 +26845,7 @@ var require_package = __commonJS({
|
|
|
26798
26845
|
"package.json"(exports, module) {
|
|
26799
26846
|
module.exports = {
|
|
26800
26847
|
name: "prjct-cli",
|
|
26801
|
-
version: "0.
|
|
26848
|
+
version: "0.64.0",
|
|
26802
26849
|
description: "Context layer for AI agents. Project context for Claude Code, Gemini CLI, and more.",
|
|
26803
26850
|
main: "core/index.ts",
|
|
26804
26851
|
bin: {
|
|
@@ -409,6 +409,34 @@ var import_node_fs3 = __toESM(require("node:fs"));
|
|
|
409
409
|
var import_node_os4 = __toESM(require("node:os"));
|
|
410
410
|
var import_node_path5 = __toESM(require("node:path"));
|
|
411
411
|
|
|
412
|
+
// core/constants/index.ts
|
|
413
|
+
var TIMEOUTS = {
|
|
414
|
+
/** Tool availability checks (git --version, npm --version) */
|
|
415
|
+
TOOL_CHECK: 5e3,
|
|
416
|
+
/** Standard git operations (status, add, commit) */
|
|
417
|
+
GIT_OPERATION: 1e4,
|
|
418
|
+
/** Git clone with --depth 1 */
|
|
419
|
+
GIT_CLONE: 6e4,
|
|
420
|
+
/** HTTP fetch/API requests */
|
|
421
|
+
API_REQUEST: 3e4,
|
|
422
|
+
/** npm install -g (CLI installation) - 2 minutes */
|
|
423
|
+
NPM_INSTALL: 12e4,
|
|
424
|
+
/** User-defined workflow hooks */
|
|
425
|
+
WORKFLOW_HOOK: 6e4
|
|
426
|
+
};
|
|
427
|
+
function getTimeout(key) {
|
|
428
|
+
const envVar = `PRJCT_TIMEOUT_${key}`;
|
|
429
|
+
const envValue = process.env[envVar];
|
|
430
|
+
if (envValue) {
|
|
431
|
+
const parsed = Number.parseInt(envValue, 10);
|
|
432
|
+
if (!Number.isNaN(parsed) && parsed > 0) {
|
|
433
|
+
return parsed;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
return TIMEOUTS[key];
|
|
437
|
+
}
|
|
438
|
+
__name(getTimeout, "getTimeout");
|
|
439
|
+
|
|
412
440
|
// core/services/dependency-validator.ts
|
|
413
441
|
var import_node_child_process = require("node:child_process");
|
|
414
442
|
|
|
@@ -1382,15 +1410,26 @@ async function installAICLI(provider) {
|
|
|
1382
1410
|
try {
|
|
1383
1411
|
console.log(`${YELLOW}\u{1F4E6} ${provider.displayName} not found. Installing...${NC}`);
|
|
1384
1412
|
console.log("");
|
|
1385
|
-
(0, import_node_child_process3.execSync)(`npm install -g ${packageName}`, {
|
|
1413
|
+
(0, import_node_child_process3.execSync)(`npm install -g ${packageName}`, {
|
|
1414
|
+
stdio: "inherit",
|
|
1415
|
+
timeout: getTimeout("NPM_INSTALL")
|
|
1416
|
+
});
|
|
1386
1417
|
console.log("");
|
|
1387
1418
|
console.log(`${GREEN}\u2713${NC} ${provider.displayName} installed successfully`);
|
|
1388
1419
|
console.log("");
|
|
1389
1420
|
return true;
|
|
1390
1421
|
} catch (error) {
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
)
|
|
1422
|
+
const err = error;
|
|
1423
|
+
const isTimeout = err.killed && err.signal === "SIGTERM";
|
|
1424
|
+
if (isTimeout) {
|
|
1425
|
+
console.log(`${YELLOW}\u26A0\uFE0F Installation timed out for ${provider.displayName}${NC}`);
|
|
1426
|
+
console.log("");
|
|
1427
|
+
console.log(`${DIM}The npm install took too long. Try:${NC}`);
|
|
1428
|
+
console.log(`${DIM} \u2022 Set PRJCT_TIMEOUT_NPM_INSTALL=300000 for 5 minutes${NC}`);
|
|
1429
|
+
console.log(`${DIM} \u2022 Run manually: npm install -g ${packageName}${NC}`);
|
|
1430
|
+
} else {
|
|
1431
|
+
console.log(`${YELLOW}\u26A0\uFE0F Failed to install ${provider.displayName}: ${err.message}${NC}`);
|
|
1432
|
+
}
|
|
1394
1433
|
console.log("");
|
|
1395
1434
|
console.log(`${DIM}Alternative installation methods:${NC}`);
|
|
1396
1435
|
console.log(`${DIM} \u2022 npm: npm install -g ${packageName}${NC}`);
|