prjct-cli 1.12.0 → 1.13.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 +40 -0
- package/core/__tests__/agentic/analysis-injection.test.ts +377 -0
- package/core/agentic/anti-hallucination.ts +23 -1
- package/core/agentic/orchestrator-executor.ts +36 -3
- package/core/agentic/prompt-builder.ts +46 -1
- package/core/types/agentic.ts +31 -0
- package/core/types/index.ts +2 -0
- package/dist/bin/prjct.mjs +104 -6
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,45 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.13.0] - 2026-02-09
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- inject sealed analysis into task prompt context (PRJ-260) (#155)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## [1.13.0] - 2026-02-09
|
|
11
|
+
|
|
12
|
+
### Features
|
|
13
|
+
- **Analysis Injection**: Sealed analysis (languages, frameworks, patterns, anti-patterns) now automatically injected into LLM prompt context (PRJ-260)
|
|
14
|
+
- **Enriched Ground Truth**: Prompt section 3 renders full analysis data — languages, frameworks, package manager, source/test dirs, code patterns, and anti-patterns
|
|
15
|
+
- **Enhanced Anti-Hallucination**: AVAILABLE tech list enriched with analysis data (case-insensitive dedup), package manager constraint added
|
|
16
|
+
|
|
17
|
+
### Implementation Details
|
|
18
|
+
|
|
19
|
+
**PRJ-260 — Inject Sync Analysis into Task Context**
|
|
20
|
+
Connected the analysis pipeline (from PRJ-263) to the prompt assembly pipeline (from PRJ-301). `analysisStorage.getActive()` returns sealed analysis (or draft fallback), loaded in parallel with real codebase context for zero latency impact.
|
|
21
|
+
|
|
22
|
+
Key changes:
|
|
23
|
+
- `core/types/agentic.ts` — New `SealedAnalysisContext` interface, extended `OrchestratorContext` with `sealedAnalysis` field
|
|
24
|
+
- `core/agentic/orchestrator-executor.ts` — Added `loadSealedAnalysis()`, loads in parallel with `gatherRealContext()`
|
|
25
|
+
- `core/agentic/prompt-builder.ts` — Section 3 (ground truth) renders analysis data, Section 5 passes analysis to anti-hallucination
|
|
26
|
+
- `core/agentic/anti-hallucination.ts` — Extended `ProjectGroundTruth` with `analysisLanguages`, `analysisFrameworks`, `analysisPackageManager`
|
|
27
|
+
- `core/__tests__/agentic/analysis-injection.test.ts` — 14 new tests
|
|
28
|
+
|
|
29
|
+
### Test Plan
|
|
30
|
+
|
|
31
|
+
#### For QA
|
|
32
|
+
1. Run `prjct sync` on a project with sealed analysis — verify prompt contains Languages, Frameworks, Patterns sections
|
|
33
|
+
2. Run `prjct sync` on a project WITHOUT sealed analysis — verify no crash, fallback rules still present
|
|
34
|
+
3. Check anti-hallucination block — verify AVAILABLE list includes analysis languages/frameworks (deduped)
|
|
35
|
+
4. Run `bun test core/__tests__/agentic/analysis-injection.test.ts` — 14 tests pass
|
|
36
|
+
5. Run `bun test` — all 770 tests pass
|
|
37
|
+
|
|
38
|
+
#### For Users
|
|
39
|
+
- **What changed:** AI prompts now include your project's detected languages, frameworks, code patterns, and anti-patterns from sealed analysis
|
|
40
|
+
- **How to use:** Run `prjct sync` then `prjct seal` — improvements are automatic in subsequent task prompts
|
|
41
|
+
- **Breaking changes:** None
|
|
42
|
+
|
|
3
43
|
## [1.12.0] - 2026-02-09
|
|
4
44
|
|
|
5
45
|
### Features
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Analysis Injection Tests (PRJ-260)
|
|
3
|
+
*
|
|
4
|
+
* Tests for injecting sealed analysis data into task context:
|
|
5
|
+
* - Prompt builder renders analysis in ground_truth section
|
|
6
|
+
* - Anti-hallucination block enriched with analysis data
|
|
7
|
+
* - Graceful degradation when no analysis available
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { beforeEach, describe, expect, it } from 'bun:test'
|
|
11
|
+
import {
|
|
12
|
+
buildAntiHallucinationBlock,
|
|
13
|
+
type ProjectGroundTruth,
|
|
14
|
+
} from '../../agentic/anti-hallucination'
|
|
15
|
+
import promptBuilder from '../../agentic/prompt-builder'
|
|
16
|
+
import type { OrchestratorContext, SealedAnalysisContext } from '../../types'
|
|
17
|
+
|
|
18
|
+
// =============================================================================
|
|
19
|
+
// Test Fixtures
|
|
20
|
+
// =============================================================================
|
|
21
|
+
|
|
22
|
+
const mockSealedAnalysis: SealedAnalysisContext = {
|
|
23
|
+
languages: ['TypeScript', 'JavaScript'],
|
|
24
|
+
frameworks: ['Hono', 'Zod'],
|
|
25
|
+
packageManager: 'bun',
|
|
26
|
+
sourceDir: 'core/',
|
|
27
|
+
testDir: 'core/__tests__/',
|
|
28
|
+
fileCount: 295,
|
|
29
|
+
patterns: [
|
|
30
|
+
{
|
|
31
|
+
name: 'StorageManager pattern',
|
|
32
|
+
description: 'All storage uses StorageManager base class with read/write/update',
|
|
33
|
+
location: 'core/storage/',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: 'Zod schemas',
|
|
37
|
+
description: 'Runtime validation with Zod for all data structures',
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
antiPatterns: [
|
|
41
|
+
{
|
|
42
|
+
issue: 'Direct fs.writeFile without StorageManager',
|
|
43
|
+
file: 'core/storage/legacy.ts',
|
|
44
|
+
suggestion: 'Use StorageManager.write() instead',
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
status: 'sealed',
|
|
48
|
+
commitHash: 'abc123def456',
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function makeOrchestratorContext(
|
|
52
|
+
sealedAnalysis: SealedAnalysisContext | null = mockSealedAnalysis
|
|
53
|
+
): OrchestratorContext {
|
|
54
|
+
return {
|
|
55
|
+
detectedDomains: ['backend', 'testing'],
|
|
56
|
+
primaryDomain: 'backend',
|
|
57
|
+
agents: [],
|
|
58
|
+
skills: [],
|
|
59
|
+
requiresFragmentation: false,
|
|
60
|
+
subtasks: null,
|
|
61
|
+
project: {
|
|
62
|
+
id: 'test-project',
|
|
63
|
+
ecosystem: 'TypeScript',
|
|
64
|
+
conventions: ['Hono', 'Zod'],
|
|
65
|
+
},
|
|
66
|
+
sealedAnalysis,
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// =============================================================================
|
|
71
|
+
// Prompt Builder — Ground Truth Section
|
|
72
|
+
// =============================================================================
|
|
73
|
+
|
|
74
|
+
describe('Analysis Injection in Prompt Builder (PRJ-260)', () => {
|
|
75
|
+
beforeEach(() => {
|
|
76
|
+
promptBuilder.resetContext()
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('should render sealed analysis in ground truth section', async () => {
|
|
80
|
+
const template = {
|
|
81
|
+
frontmatter: { description: 'Test task' },
|
|
82
|
+
content: '## Instructions\nDo the work',
|
|
83
|
+
}
|
|
84
|
+
const context = { projectPath: '/test/project', files: ['a.ts'] }
|
|
85
|
+
const orcCtx = makeOrchestratorContext()
|
|
86
|
+
|
|
87
|
+
const prompt = await promptBuilder.build(
|
|
88
|
+
template,
|
|
89
|
+
context,
|
|
90
|
+
{},
|
|
91
|
+
null,
|
|
92
|
+
null,
|
|
93
|
+
null,
|
|
94
|
+
null,
|
|
95
|
+
null,
|
|
96
|
+
orcCtx
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
// Should contain analysis data
|
|
100
|
+
expect(prompt).toContain('Languages**: TypeScript, JavaScript')
|
|
101
|
+
expect(prompt).toContain('Frameworks**: Hono, Zod')
|
|
102
|
+
expect(prompt).toContain('Package Manager**: bun')
|
|
103
|
+
expect(prompt).toContain('Source Dir**: core/')
|
|
104
|
+
expect(prompt).toContain('Test Dir**: core/__tests__/')
|
|
105
|
+
expect(prompt).toContain('Files Analyzed**: 295')
|
|
106
|
+
expect(prompt).toContain('Analysis Status**: sealed')
|
|
107
|
+
expect(prompt).toContain('abc123de') // truncated commit hash
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it('should render code patterns from sealed analysis', async () => {
|
|
111
|
+
const template = {
|
|
112
|
+
frontmatter: { description: 'Test' },
|
|
113
|
+
content: '## Do',
|
|
114
|
+
}
|
|
115
|
+
const context = { projectPath: '/test', files: [] }
|
|
116
|
+
const orcCtx = makeOrchestratorContext()
|
|
117
|
+
|
|
118
|
+
const prompt = await promptBuilder.build(
|
|
119
|
+
template,
|
|
120
|
+
context,
|
|
121
|
+
{},
|
|
122
|
+
null,
|
|
123
|
+
null,
|
|
124
|
+
null,
|
|
125
|
+
null,
|
|
126
|
+
null,
|
|
127
|
+
orcCtx
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
expect(prompt).toContain('Code Patterns (Follow These)')
|
|
131
|
+
expect(prompt).toContain('StorageManager pattern')
|
|
132
|
+
expect(prompt).toContain('All storage uses StorageManager base class')
|
|
133
|
+
expect(prompt).toContain('core/storage/')
|
|
134
|
+
expect(prompt).toContain('Zod schemas')
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
it('should render anti-patterns from sealed analysis', async () => {
|
|
138
|
+
const template = {
|
|
139
|
+
frontmatter: { description: 'Test' },
|
|
140
|
+
content: '## Do',
|
|
141
|
+
}
|
|
142
|
+
const context = { projectPath: '/test', files: [] }
|
|
143
|
+
const orcCtx = makeOrchestratorContext()
|
|
144
|
+
|
|
145
|
+
const prompt = await promptBuilder.build(
|
|
146
|
+
template,
|
|
147
|
+
context,
|
|
148
|
+
{},
|
|
149
|
+
null,
|
|
150
|
+
null,
|
|
151
|
+
null,
|
|
152
|
+
null,
|
|
153
|
+
null,
|
|
154
|
+
orcCtx
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
expect(prompt).toContain('Anti-Patterns (Avoid These)')
|
|
158
|
+
expect(prompt).toContain('Direct fs.writeFile without StorageManager')
|
|
159
|
+
expect(prompt).toContain('core/storage/legacy.ts')
|
|
160
|
+
expect(prompt).toContain('Use StorageManager.write() instead')
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
it('should gracefully handle null sealed analysis', async () => {
|
|
164
|
+
const template = {
|
|
165
|
+
frontmatter: { description: 'Test' },
|
|
166
|
+
content: '## Do',
|
|
167
|
+
}
|
|
168
|
+
const context = { projectPath: '/test', files: [] }
|
|
169
|
+
const orcCtx = makeOrchestratorContext(null)
|
|
170
|
+
|
|
171
|
+
const prompt = await promptBuilder.build(
|
|
172
|
+
template,
|
|
173
|
+
context,
|
|
174
|
+
{},
|
|
175
|
+
null,
|
|
176
|
+
null,
|
|
177
|
+
null,
|
|
178
|
+
null,
|
|
179
|
+
null,
|
|
180
|
+
orcCtx
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
// Should still have basic project analysis
|
|
184
|
+
expect(prompt).toContain('PROJECT ANALYSIS')
|
|
185
|
+
expect(prompt).toContain('Ecosystem**: TypeScript')
|
|
186
|
+
// Should NOT have analysis-specific fields
|
|
187
|
+
expect(prompt).not.toContain('Languages**:')
|
|
188
|
+
expect(prompt).not.toContain('Code Patterns (Follow These)')
|
|
189
|
+
expect(prompt).not.toContain('Anti-Patterns (Avoid These)')
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
it('should handle empty patterns and anti-patterns', async () => {
|
|
193
|
+
const template = {
|
|
194
|
+
frontmatter: { description: 'Test' },
|
|
195
|
+
content: '## Do',
|
|
196
|
+
}
|
|
197
|
+
const context = { projectPath: '/test', files: [] }
|
|
198
|
+
const emptyAnalysis: SealedAnalysisContext = {
|
|
199
|
+
...mockSealedAnalysis,
|
|
200
|
+
patterns: [],
|
|
201
|
+
antiPatterns: [],
|
|
202
|
+
}
|
|
203
|
+
const orcCtx = makeOrchestratorContext(emptyAnalysis)
|
|
204
|
+
|
|
205
|
+
const prompt = await promptBuilder.build(
|
|
206
|
+
template,
|
|
207
|
+
context,
|
|
208
|
+
{},
|
|
209
|
+
null,
|
|
210
|
+
null,
|
|
211
|
+
null,
|
|
212
|
+
null,
|
|
213
|
+
null,
|
|
214
|
+
orcCtx
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
expect(prompt).toContain('Languages**: TypeScript, JavaScript')
|
|
218
|
+
expect(prompt).not.toContain('Code Patterns (Follow These)')
|
|
219
|
+
expect(prompt).not.toContain('Anti-Patterns (Avoid These)')
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
it('should handle no orchestrator context at all', async () => {
|
|
223
|
+
const template = {
|
|
224
|
+
frontmatter: { description: 'Test' },
|
|
225
|
+
content: '## Do',
|
|
226
|
+
}
|
|
227
|
+
const context = { projectPath: '/test', files: [] }
|
|
228
|
+
|
|
229
|
+
const prompt = await promptBuilder.build(
|
|
230
|
+
template,
|
|
231
|
+
context,
|
|
232
|
+
{},
|
|
233
|
+
null,
|
|
234
|
+
null,
|
|
235
|
+
null,
|
|
236
|
+
null,
|
|
237
|
+
null,
|
|
238
|
+
null
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
// Should not crash, should have fallback rules
|
|
242
|
+
expect(prompt).toContain('CONSTRAINTS')
|
|
243
|
+
expect(prompt).not.toContain('PROJECT ANALYSIS')
|
|
244
|
+
})
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
// =============================================================================
|
|
248
|
+
// Anti-Hallucination Block — Analysis Enrichment
|
|
249
|
+
// =============================================================================
|
|
250
|
+
|
|
251
|
+
describe('Anti-Hallucination Block with Analysis (PRJ-260)', () => {
|
|
252
|
+
it('should include analysis languages in AVAILABLE list', () => {
|
|
253
|
+
const truth: ProjectGroundTruth = {
|
|
254
|
+
projectPath: '/test',
|
|
255
|
+
language: 'TypeScript',
|
|
256
|
+
techStack: ['Hono'],
|
|
257
|
+
analysisLanguages: ['TypeScript', 'JavaScript', 'Shell'],
|
|
258
|
+
analysisFrameworks: ['Hono', 'Vitest'],
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const block = buildAntiHallucinationBlock(truth)
|
|
262
|
+
|
|
263
|
+
expect(block).toContain('AVAILABLE in this project:')
|
|
264
|
+
// TypeScript and Hono should not be duplicated
|
|
265
|
+
expect(block).toContain('JavaScript')
|
|
266
|
+
expect(block).toContain('Shell')
|
|
267
|
+
expect(block).toContain('Vitest')
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
it('should deduplicate analysis data with existing tech stack (case-insensitive)', () => {
|
|
271
|
+
const truth: ProjectGroundTruth = {
|
|
272
|
+
projectPath: '/test',
|
|
273
|
+
language: 'TypeScript',
|
|
274
|
+
framework: 'Hono',
|
|
275
|
+
techStack: ['Zod'],
|
|
276
|
+
analysisLanguages: ['typescript'], // lowercase duplicate
|
|
277
|
+
analysisFrameworks: ['hono', 'Zod'], // lowercase duplicates
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const block = buildAntiHallucinationBlock(truth)
|
|
281
|
+
|
|
282
|
+
// Count occurrences — each should appear exactly once
|
|
283
|
+
const typescriptMatches = block.match(/typescript/gi)
|
|
284
|
+
expect(typescriptMatches?.length).toBe(1)
|
|
285
|
+
|
|
286
|
+
const honoMatches = block.match(/hono/gi)
|
|
287
|
+
expect(honoMatches?.length).toBe(1)
|
|
288
|
+
})
|
|
289
|
+
|
|
290
|
+
it('should show package manager from analysis', () => {
|
|
291
|
+
const truth: ProjectGroundTruth = {
|
|
292
|
+
projectPath: '/test',
|
|
293
|
+
analysisPackageManager: 'bun',
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const block = buildAntiHallucinationBlock(truth)
|
|
297
|
+
|
|
298
|
+
expect(block).toContain('PACKAGE MANAGER: bun')
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
it('should work without any analysis data', () => {
|
|
302
|
+
const truth: ProjectGroundTruth = {
|
|
303
|
+
projectPath: '/test',
|
|
304
|
+
language: 'Python',
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const block = buildAntiHallucinationBlock(truth)
|
|
308
|
+
|
|
309
|
+
expect(block).toContain('AVAILABLE in this project: Python')
|
|
310
|
+
expect(block).not.toContain('PACKAGE MANAGER:')
|
|
311
|
+
})
|
|
312
|
+
|
|
313
|
+
it('should handle empty analysis arrays gracefully', () => {
|
|
314
|
+
const truth: ProjectGroundTruth = {
|
|
315
|
+
projectPath: '/test',
|
|
316
|
+
analysisLanguages: [],
|
|
317
|
+
analysisFrameworks: [],
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const block = buildAntiHallucinationBlock(truth)
|
|
321
|
+
|
|
322
|
+
// Should not contain AVAILABLE line (no tech to show)
|
|
323
|
+
expect(block).not.toContain('AVAILABLE in this project:')
|
|
324
|
+
expect(block).toContain('CONSTRAINTS')
|
|
325
|
+
})
|
|
326
|
+
})
|
|
327
|
+
|
|
328
|
+
// =============================================================================
|
|
329
|
+
// SealedAnalysisContext Type
|
|
330
|
+
// =============================================================================
|
|
331
|
+
|
|
332
|
+
describe('SealedAnalysisContext type (PRJ-260)', () => {
|
|
333
|
+
it('should accept valid sealed analysis data', () => {
|
|
334
|
+
const analysis: SealedAnalysisContext = {
|
|
335
|
+
languages: ['TypeScript'],
|
|
336
|
+
frameworks: ['Hono'],
|
|
337
|
+
fileCount: 100,
|
|
338
|
+
patterns: [{ name: 'test', description: 'test pattern' }],
|
|
339
|
+
antiPatterns: [{ issue: 'test', file: 'test.ts', suggestion: 'fix it' }],
|
|
340
|
+
status: 'sealed',
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
expect(analysis.languages).toEqual(['TypeScript'])
|
|
344
|
+
expect(analysis.status).toBe('sealed')
|
|
345
|
+
})
|
|
346
|
+
|
|
347
|
+
it('should accept draft status', () => {
|
|
348
|
+
const analysis: SealedAnalysisContext = {
|
|
349
|
+
languages: [],
|
|
350
|
+
frameworks: [],
|
|
351
|
+
fileCount: 0,
|
|
352
|
+
patterns: [],
|
|
353
|
+
antiPatterns: [],
|
|
354
|
+
status: 'draft',
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
expect(analysis.status).toBe('draft')
|
|
358
|
+
})
|
|
359
|
+
|
|
360
|
+
it('should accept optional fields', () => {
|
|
361
|
+
const analysis: SealedAnalysisContext = {
|
|
362
|
+
languages: ['Python'],
|
|
363
|
+
frameworks: [],
|
|
364
|
+
fileCount: 50,
|
|
365
|
+
patterns: [],
|
|
366
|
+
antiPatterns: [],
|
|
367
|
+
status: 'sealed',
|
|
368
|
+
packageManager: 'pip',
|
|
369
|
+
sourceDir: 'src/',
|
|
370
|
+
testDir: 'tests/',
|
|
371
|
+
commitHash: 'abc123',
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
expect(analysis.packageManager).toBe('pip')
|
|
375
|
+
expect(analysis.commitHash).toBe('abc123')
|
|
376
|
+
})
|
|
377
|
+
})
|
|
@@ -47,6 +47,12 @@ export const ProjectGroundTruthSchema = z.object({
|
|
|
47
47
|
fileCount: z.number().optional(),
|
|
48
48
|
/** Available agent names (e.g., ['backend', 'testing']) */
|
|
49
49
|
availableAgents: z.array(z.string()).default([]),
|
|
50
|
+
/** Sealed analysis languages — used to ground available tech (PRJ-260) */
|
|
51
|
+
analysisLanguages: z.array(z.string()).default([]),
|
|
52
|
+
/** Sealed analysis frameworks — used to ground available tech (PRJ-260) */
|
|
53
|
+
analysisFrameworks: z.array(z.string()).default([]),
|
|
54
|
+
/** Package manager from sealed analysis (PRJ-260) */
|
|
55
|
+
analysisPackageManager: z.string().optional(),
|
|
50
56
|
})
|
|
51
57
|
|
|
52
58
|
export type ProjectGroundTruth = z.input<typeof ProjectGroundTruthSchema>
|
|
@@ -79,16 +85,32 @@ export function buildAntiHallucinationBlock(truth: ProjectGroundTruth): string {
|
|
|
79
85
|
|
|
80
86
|
parts.push('## CONSTRAINTS (Read Before Acting)\n')
|
|
81
87
|
|
|
82
|
-
// 1. Explicit availability
|
|
88
|
+
// 1. Explicit availability (enriched by sealed analysis — PRJ-260)
|
|
83
89
|
const available: string[] = []
|
|
84
90
|
if (truth.language) available.push(truth.language)
|
|
85
91
|
if (truth.framework) available.push(truth.framework)
|
|
86
92
|
const techStack = truth.techStack ?? []
|
|
87
93
|
available.push(...techStack.filter((t) => t !== truth.framework))
|
|
94
|
+
// Merge languages/frameworks from sealed analysis (deduped)
|
|
95
|
+
const analysisLangs = truth.analysisLanguages ?? []
|
|
96
|
+
const analysisFrameworks = truth.analysisFrameworks ?? []
|
|
97
|
+
for (const lang of analysisLangs) {
|
|
98
|
+
if (!available.some((a) => a.toLowerCase() === lang.toLowerCase())) {
|
|
99
|
+
available.push(lang)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
for (const fw of analysisFrameworks) {
|
|
103
|
+
if (!available.some((a) => a.toLowerCase() === fw.toLowerCase())) {
|
|
104
|
+
available.push(fw)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
88
107
|
|
|
89
108
|
if (available.length > 0) {
|
|
90
109
|
parts.push(`AVAILABLE in this project: ${available.join(', ')}`)
|
|
91
110
|
}
|
|
111
|
+
if (truth.analysisPackageManager) {
|
|
112
|
+
parts.push(`PACKAGE MANAGER: ${truth.analysisPackageManager}`)
|
|
113
|
+
}
|
|
92
114
|
|
|
93
115
|
// 2. Explicit unavailability from domain flags
|
|
94
116
|
if (truth.domains) {
|
|
@@ -23,13 +23,14 @@ import { getRecentFiles } from '../context-tools/recent-tool'
|
|
|
23
23
|
import { extractSignatures } from '../context-tools/signatures-tool'
|
|
24
24
|
import configManager from '../infrastructure/config-manager'
|
|
25
25
|
import pathManager from '../infrastructure/path-manager'
|
|
26
|
-
import { stateStorage } from '../storage'
|
|
26
|
+
import { analysisStorage, stateStorage } from '../storage'
|
|
27
27
|
import type {
|
|
28
28
|
LoadedAgent,
|
|
29
29
|
LoadedSkill,
|
|
30
30
|
OrchestratorContext,
|
|
31
31
|
OrchestratorSubtask,
|
|
32
32
|
RealCodebaseContext,
|
|
33
|
+
SealedAnalysisContext,
|
|
33
34
|
} from '../types'
|
|
34
35
|
import { getErrorMessage, isNotFoundError } from '../types/fs'
|
|
35
36
|
import domainClassifier, { type ProjectContext } from './domain-classifier'
|
|
@@ -76,8 +77,11 @@ export class OrchestratorExecutor {
|
|
|
76
77
|
// Step 5: Load skills from agent frontmatter
|
|
77
78
|
const skills = await this.loadSkills(agents)
|
|
78
79
|
|
|
79
|
-
// Step 6: Gather real codebase context
|
|
80
|
-
const realContext = await
|
|
80
|
+
// Step 6: Gather real codebase context and sealed analysis in parallel
|
|
81
|
+
const [realContext, sealedAnalysis] = await Promise.all([
|
|
82
|
+
this.gatherRealContext(taskDescription, projectPath),
|
|
83
|
+
this.loadSealedAnalysis(projectId),
|
|
84
|
+
])
|
|
81
85
|
|
|
82
86
|
// Step 7: Determine if fragmentation is needed
|
|
83
87
|
const requiresFragmentation = this.shouldFragment(domains, taskDescription)
|
|
@@ -101,6 +105,7 @@ export class OrchestratorExecutor {
|
|
|
101
105
|
conventions: repoAnalysis?.conventions || [],
|
|
102
106
|
},
|
|
103
107
|
realContext,
|
|
108
|
+
sealedAnalysis,
|
|
104
109
|
}
|
|
105
110
|
}
|
|
106
111
|
|
|
@@ -197,6 +202,34 @@ export class OrchestratorExecutor {
|
|
|
197
202
|
}
|
|
198
203
|
}
|
|
199
204
|
|
|
205
|
+
/**
|
|
206
|
+
* Load sealed/active analysis from analysis storage (PRJ-260).
|
|
207
|
+
* Returns sealed if available, otherwise draft as fallback.
|
|
208
|
+
* Returns null if no analysis exists (graceful degradation).
|
|
209
|
+
*/
|
|
210
|
+
private async loadSealedAnalysis(projectId: string): Promise<SealedAnalysisContext | null> {
|
|
211
|
+
try {
|
|
212
|
+
const analysis = await analysisStorage.getActive(projectId)
|
|
213
|
+
if (!analysis) return null
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
languages: analysis.languages,
|
|
217
|
+
frameworks: analysis.frameworks,
|
|
218
|
+
packageManager: analysis.packageManager,
|
|
219
|
+
sourceDir: analysis.sourceDir,
|
|
220
|
+
testDir: analysis.testDir,
|
|
221
|
+
fileCount: analysis.fileCount,
|
|
222
|
+
patterns: analysis.patterns,
|
|
223
|
+
antiPatterns: analysis.antiPatterns,
|
|
224
|
+
status: analysis.status ?? 'draft',
|
|
225
|
+
commitHash: analysis.commitHash,
|
|
226
|
+
}
|
|
227
|
+
} catch {
|
|
228
|
+
// Graceful degradation — analysis is optional enhancement
|
|
229
|
+
return null
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
200
233
|
/**
|
|
201
234
|
* Load repo-analysis.json for project context
|
|
202
235
|
*/
|
|
@@ -542,10 +542,50 @@ class PromptBuilder {
|
|
|
542
542
|
// =========================================================================
|
|
543
543
|
|
|
544
544
|
if (orchestratorContext) {
|
|
545
|
+
const sa = orchestratorContext.sealedAnalysis
|
|
545
546
|
parts.push('\n## PROJECT ANALYSIS (Sealed)\n')
|
|
546
547
|
parts.push(`**Ecosystem**: ${orchestratorContext.project.ecosystem}\n`)
|
|
547
548
|
parts.push(`**Primary Domain**: ${orchestratorContext.primaryDomain}\n`)
|
|
548
|
-
parts.push(`**Domains**: ${orchestratorContext.detectedDomains.join(', ')}\n
|
|
549
|
+
parts.push(`**Domains**: ${orchestratorContext.detectedDomains.join(', ')}\n`)
|
|
550
|
+
|
|
551
|
+
// Inject sealed analysis data (PRJ-260)
|
|
552
|
+
if (sa) {
|
|
553
|
+
if (sa.languages.length > 0) {
|
|
554
|
+
parts.push(`**Languages**: ${sa.languages.join(', ')}\n`)
|
|
555
|
+
}
|
|
556
|
+
if (sa.frameworks.length > 0) {
|
|
557
|
+
parts.push(`**Frameworks**: ${sa.frameworks.join(', ')}\n`)
|
|
558
|
+
}
|
|
559
|
+
if (sa.packageManager) {
|
|
560
|
+
parts.push(`**Package Manager**: ${sa.packageManager}\n`)
|
|
561
|
+
}
|
|
562
|
+
if (sa.sourceDir) {
|
|
563
|
+
parts.push(`**Source Dir**: ${sa.sourceDir}\n`)
|
|
564
|
+
}
|
|
565
|
+
if (sa.testDir) {
|
|
566
|
+
parts.push(`**Test Dir**: ${sa.testDir}\n`)
|
|
567
|
+
}
|
|
568
|
+
parts.push(`**Files Analyzed**: ${sa.fileCount}\n`)
|
|
569
|
+
parts.push(
|
|
570
|
+
`**Analysis Status**: ${sa.status}${sa.commitHash ? ` (commit: ${sa.commitHash.slice(0, 8)})` : ''}\n`
|
|
571
|
+
)
|
|
572
|
+
|
|
573
|
+
if (sa.patterns.length > 0) {
|
|
574
|
+
parts.push('\n### Code Patterns (Follow These)\n')
|
|
575
|
+
for (const p of sa.patterns) {
|
|
576
|
+
parts.push(`- **${p.name}**: ${p.description}${p.location ? ` (${p.location})` : ''}\n`)
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
if (sa.antiPatterns.length > 0) {
|
|
581
|
+
parts.push('\n### Anti-Patterns (Avoid These)\n')
|
|
582
|
+
for (const ap of sa.antiPatterns) {
|
|
583
|
+
parts.push(`- **${ap.issue}** in \`${ap.file}\` — ${ap.suggestion}\n`)
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
parts.push('\n')
|
|
549
589
|
}
|
|
550
590
|
|
|
551
591
|
const needsPatterns = commandContext.patterns
|
|
@@ -648,6 +688,7 @@ class PromptBuilder {
|
|
|
648
688
|
// =========================================================================
|
|
649
689
|
|
|
650
690
|
if (projectPath) {
|
|
691
|
+
const sa = orchestratorContext?.sealedAnalysis
|
|
651
692
|
const groundTruth: ProjectGroundTruth = {
|
|
652
693
|
projectPath,
|
|
653
694
|
language: orchestratorContext?.project?.ecosystem,
|
|
@@ -655,6 +696,10 @@ class PromptBuilder {
|
|
|
655
696
|
domains: this.extractDomains(state),
|
|
656
697
|
fileCount: context.files?.length || context.filteredSize || 0,
|
|
657
698
|
availableAgents: orchestratorContext?.agents?.map((a) => a.name) || [],
|
|
699
|
+
// Inject sealed analysis data for enriched grounding (PRJ-260)
|
|
700
|
+
analysisLanguages: sa?.languages || [],
|
|
701
|
+
analysisFrameworks: sa?.frameworks || [],
|
|
702
|
+
analysisPackageManager: sa?.packageManager,
|
|
658
703
|
}
|
|
659
704
|
parts.push(`\n${buildAntiHallucinationBlock(groundTruth)}\n`)
|
|
660
705
|
} else {
|
package/core/types/agentic.ts
CHANGED
|
@@ -724,4 +724,35 @@ export interface OrchestratorContext {
|
|
|
724
724
|
}
|
|
725
725
|
/** Real codebase context gathered proactively */
|
|
726
726
|
realContext?: RealCodebaseContext
|
|
727
|
+
/** Sealed/active analysis from PRJ-263 storage — injected into prompt context (PRJ-260) */
|
|
728
|
+
sealedAnalysis?: SealedAnalysisContext | null
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
/**
|
|
732
|
+
* Subset of analysis data injected into prompt context.
|
|
733
|
+
* Extracted from AnalysisSchema to avoid coupling types to storage schema.
|
|
734
|
+
*
|
|
735
|
+
* @see PRJ-260
|
|
736
|
+
*/
|
|
737
|
+
export interface SealedAnalysisContext {
|
|
738
|
+
/** Programming languages detected */
|
|
739
|
+
languages: string[]
|
|
740
|
+
/** Frameworks detected */
|
|
741
|
+
frameworks: string[]
|
|
742
|
+
/** Package manager (e.g., 'bun', 'npm', 'pnpm') */
|
|
743
|
+
packageManager?: string
|
|
744
|
+
/** Source directory */
|
|
745
|
+
sourceDir?: string
|
|
746
|
+
/** Test directory */
|
|
747
|
+
testDir?: string
|
|
748
|
+
/** Total files analyzed */
|
|
749
|
+
fileCount: number
|
|
750
|
+
/** Code patterns found */
|
|
751
|
+
patterns: Array<{ name: string; description: string; location?: string }>
|
|
752
|
+
/** Anti-patterns found */
|
|
753
|
+
antiPatterns: Array<{ issue: string; file: string; suggestion: string }>
|
|
754
|
+
/** Lifecycle status */
|
|
755
|
+
status: 'draft' | 'verified' | 'sealed'
|
|
756
|
+
/** Git commit hash when analysis was performed */
|
|
757
|
+
commitHash?: string
|
|
727
758
|
}
|
package/core/types/index.ts
CHANGED
package/dist/bin/prjct.mjs
CHANGED
|
@@ -14203,7 +14203,10 @@ var init_orchestrator_executor = __esm({
|
|
|
14203
14203
|
const { domains, primary } = await this.detectDomains(taskDescription, projectId, repoAnalysis);
|
|
14204
14204
|
const agents = await this.loadAgents(domains, projectId);
|
|
14205
14205
|
const skills = await this.loadSkills(agents);
|
|
14206
|
-
const realContext = await
|
|
14206
|
+
const [realContext, sealedAnalysis] = await Promise.all([
|
|
14207
|
+
this.gatherRealContext(taskDescription, projectPath),
|
|
14208
|
+
this.loadSealedAnalysis(projectId)
|
|
14209
|
+
]);
|
|
14207
14210
|
const requiresFragmentation = this.shouldFragment(domains, taskDescription);
|
|
14208
14211
|
let subtasks = null;
|
|
14209
14212
|
if (requiresFragmentation && command === "task") {
|
|
@@ -14221,7 +14224,8 @@ var init_orchestrator_executor = __esm({
|
|
|
14221
14224
|
ecosystem: repoAnalysis?.ecosystem || "unknown",
|
|
14222
14225
|
conventions: repoAnalysis?.conventions || []
|
|
14223
14226
|
},
|
|
14224
|
-
realContext
|
|
14227
|
+
realContext,
|
|
14228
|
+
sealedAnalysis
|
|
14225
14229
|
};
|
|
14226
14230
|
}
|
|
14227
14231
|
/**
|
|
@@ -14301,6 +14305,31 @@ var init_orchestrator_executor = __esm({
|
|
|
14301
14305
|
return { branch: "unknown", status: "git unavailable" };
|
|
14302
14306
|
}
|
|
14303
14307
|
}
|
|
14308
|
+
/**
|
|
14309
|
+
* Load sealed/active analysis from analysis storage (PRJ-260).
|
|
14310
|
+
* Returns sealed if available, otherwise draft as fallback.
|
|
14311
|
+
* Returns null if no analysis exists (graceful degradation).
|
|
14312
|
+
*/
|
|
14313
|
+
async loadSealedAnalysis(projectId) {
|
|
14314
|
+
try {
|
|
14315
|
+
const analysis2 = await analysisStorage.getActive(projectId);
|
|
14316
|
+
if (!analysis2) return null;
|
|
14317
|
+
return {
|
|
14318
|
+
languages: analysis2.languages,
|
|
14319
|
+
frameworks: analysis2.frameworks,
|
|
14320
|
+
packageManager: analysis2.packageManager,
|
|
14321
|
+
sourceDir: analysis2.sourceDir,
|
|
14322
|
+
testDir: analysis2.testDir,
|
|
14323
|
+
fileCount: analysis2.fileCount,
|
|
14324
|
+
patterns: analysis2.patterns,
|
|
14325
|
+
antiPatterns: analysis2.antiPatterns,
|
|
14326
|
+
status: analysis2.status ?? "draft",
|
|
14327
|
+
commitHash: analysis2.commitHash
|
|
14328
|
+
};
|
|
14329
|
+
} catch {
|
|
14330
|
+
return null;
|
|
14331
|
+
}
|
|
14332
|
+
}
|
|
14304
14333
|
/**
|
|
14305
14334
|
* Load repo-analysis.json for project context
|
|
14306
14335
|
*/
|
|
@@ -15381,9 +15410,24 @@ function buildAntiHallucinationBlock(truth) {
|
|
|
15381
15410
|
if (truth.framework) available.push(truth.framework);
|
|
15382
15411
|
const techStack = truth.techStack ?? [];
|
|
15383
15412
|
available.push(...techStack.filter((t) => t !== truth.framework));
|
|
15413
|
+
const analysisLangs = truth.analysisLanguages ?? [];
|
|
15414
|
+
const analysisFrameworks = truth.analysisFrameworks ?? [];
|
|
15415
|
+
for (const lang of analysisLangs) {
|
|
15416
|
+
if (!available.some((a) => a.toLowerCase() === lang.toLowerCase())) {
|
|
15417
|
+
available.push(lang);
|
|
15418
|
+
}
|
|
15419
|
+
}
|
|
15420
|
+
for (const fw of analysisFrameworks) {
|
|
15421
|
+
if (!available.some((a) => a.toLowerCase() === fw.toLowerCase())) {
|
|
15422
|
+
available.push(fw);
|
|
15423
|
+
}
|
|
15424
|
+
}
|
|
15384
15425
|
if (available.length > 0) {
|
|
15385
15426
|
parts.push(`AVAILABLE in this project: ${available.join(", ")}`);
|
|
15386
15427
|
}
|
|
15428
|
+
if (truth.analysisPackageManager) {
|
|
15429
|
+
parts.push(`PACKAGE MANAGER: ${truth.analysisPackageManager}`);
|
|
15430
|
+
}
|
|
15387
15431
|
if (truth.domains) {
|
|
15388
15432
|
const absent = Object.entries(truth.domains).filter(([, hasIt]) => !hasIt).map(([key]) => DOMAIN_LABELS[key]).filter(Boolean);
|
|
15389
15433
|
if (absent.length > 0) {
|
|
@@ -15430,7 +15474,13 @@ var init_anti_hallucination = __esm({
|
|
|
15430
15474
|
/** Total files in project */
|
|
15431
15475
|
fileCount: z15.number().optional(),
|
|
15432
15476
|
/** Available agent names (e.g., ['backend', 'testing']) */
|
|
15433
|
-
availableAgents: z15.array(z15.string()).default([])
|
|
15477
|
+
availableAgents: z15.array(z15.string()).default([]),
|
|
15478
|
+
/** Sealed analysis languages — used to ground available tech (PRJ-260) */
|
|
15479
|
+
analysisLanguages: z15.array(z15.string()).default([]),
|
|
15480
|
+
/** Sealed analysis frameworks — used to ground available tech (PRJ-260) */
|
|
15481
|
+
analysisFrameworks: z15.array(z15.string()).default([]),
|
|
15482
|
+
/** Package manager from sealed analysis (PRJ-260) */
|
|
15483
|
+
analysisPackageManager: z15.string().optional()
|
|
15434
15484
|
});
|
|
15435
15485
|
DOMAIN_LABELS = {
|
|
15436
15486
|
hasFrontend: "Frontend (UI/components)",
|
|
@@ -16174,14 +16224,57 @@ ${envBlock}
|
|
|
16174
16224
|
`);
|
|
16175
16225
|
}
|
|
16176
16226
|
if (orchestratorContext) {
|
|
16227
|
+
const sa = orchestratorContext.sealedAnalysis;
|
|
16177
16228
|
parts.push("\n## PROJECT ANALYSIS (Sealed)\n");
|
|
16178
16229
|
parts.push(`**Ecosystem**: ${orchestratorContext.project.ecosystem}
|
|
16179
16230
|
`);
|
|
16180
16231
|
parts.push(`**Primary Domain**: ${orchestratorContext.primaryDomain}
|
|
16181
16232
|
`);
|
|
16182
16233
|
parts.push(`**Domains**: ${orchestratorContext.detectedDomains.join(", ")}
|
|
16183
|
-
|
|
16184
16234
|
`);
|
|
16235
|
+
if (sa) {
|
|
16236
|
+
if (sa.languages.length > 0) {
|
|
16237
|
+
parts.push(`**Languages**: ${sa.languages.join(", ")}
|
|
16238
|
+
`);
|
|
16239
|
+
}
|
|
16240
|
+
if (sa.frameworks.length > 0) {
|
|
16241
|
+
parts.push(`**Frameworks**: ${sa.frameworks.join(", ")}
|
|
16242
|
+
`);
|
|
16243
|
+
}
|
|
16244
|
+
if (sa.packageManager) {
|
|
16245
|
+
parts.push(`**Package Manager**: ${sa.packageManager}
|
|
16246
|
+
`);
|
|
16247
|
+
}
|
|
16248
|
+
if (sa.sourceDir) {
|
|
16249
|
+
parts.push(`**Source Dir**: ${sa.sourceDir}
|
|
16250
|
+
`);
|
|
16251
|
+
}
|
|
16252
|
+
if (sa.testDir) {
|
|
16253
|
+
parts.push(`**Test Dir**: ${sa.testDir}
|
|
16254
|
+
`);
|
|
16255
|
+
}
|
|
16256
|
+
parts.push(`**Files Analyzed**: ${sa.fileCount}
|
|
16257
|
+
`);
|
|
16258
|
+
parts.push(
|
|
16259
|
+
`**Analysis Status**: ${sa.status}${sa.commitHash ? ` (commit: ${sa.commitHash.slice(0, 8)})` : ""}
|
|
16260
|
+
`
|
|
16261
|
+
);
|
|
16262
|
+
if (sa.patterns.length > 0) {
|
|
16263
|
+
parts.push("\n### Code Patterns (Follow These)\n");
|
|
16264
|
+
for (const p of sa.patterns) {
|
|
16265
|
+
parts.push(`- **${p.name}**: ${p.description}${p.location ? ` (${p.location})` : ""}
|
|
16266
|
+
`);
|
|
16267
|
+
}
|
|
16268
|
+
}
|
|
16269
|
+
if (sa.antiPatterns.length > 0) {
|
|
16270
|
+
parts.push("\n### Anti-Patterns (Avoid These)\n");
|
|
16271
|
+
for (const ap of sa.antiPatterns) {
|
|
16272
|
+
parts.push(`- **${ap.issue}** in \`${ap.file}\` \u2014 ${ap.suggestion}
|
|
16273
|
+
`);
|
|
16274
|
+
}
|
|
16275
|
+
}
|
|
16276
|
+
}
|
|
16277
|
+
parts.push("\n");
|
|
16185
16278
|
}
|
|
16186
16279
|
const needsPatterns = commandContext.patterns;
|
|
16187
16280
|
const codePatternsContent = state?.codePatterns || "";
|
|
@@ -16282,13 +16375,18 @@ Show changes, list affected files, ask for confirmation.
|
|
|
16282
16375
|
);
|
|
16283
16376
|
}
|
|
16284
16377
|
if (projectPath) {
|
|
16378
|
+
const sa = orchestratorContext?.sealedAnalysis;
|
|
16285
16379
|
const groundTruth2 = {
|
|
16286
16380
|
projectPath,
|
|
16287
16381
|
language: orchestratorContext?.project?.ecosystem,
|
|
16288
16382
|
techStack: orchestratorContext?.project?.conventions || [],
|
|
16289
16383
|
domains: this.extractDomains(state),
|
|
16290
16384
|
fileCount: context2.files?.length || context2.filteredSize || 0,
|
|
16291
|
-
availableAgents: orchestratorContext?.agents?.map((a) => a.name) || []
|
|
16385
|
+
availableAgents: orchestratorContext?.agents?.map((a) => a.name) || [],
|
|
16386
|
+
// Inject sealed analysis data for enriched grounding (PRJ-260)
|
|
16387
|
+
analysisLanguages: sa?.languages || [],
|
|
16388
|
+
analysisFrameworks: sa?.frameworks || [],
|
|
16389
|
+
analysisPackageManager: sa?.packageManager
|
|
16292
16390
|
};
|
|
16293
16391
|
parts.push(`
|
|
16294
16392
|
${buildAntiHallucinationBlock(groundTruth2)}
|
|
@@ -31102,7 +31200,7 @@ var require_package = __commonJS({
|
|
|
31102
31200
|
"package.json"(exports, module) {
|
|
31103
31201
|
module.exports = {
|
|
31104
31202
|
name: "prjct-cli",
|
|
31105
|
-
version: "1.
|
|
31203
|
+
version: "1.13.0",
|
|
31106
31204
|
description: "Context layer for AI agents. Project context for Claude Code, Gemini CLI, and more.",
|
|
31107
31205
|
main: "core/index.ts",
|
|
31108
31206
|
bin: {
|