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 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 proactively
80
- const realContext = await this.gatherRealContext(taskDescription, projectPath)
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\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 {
@@ -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
  }
@@ -81,6 +81,8 @@ export type {
81
81
  RealCodebaseContext,
82
82
  ReasoningResult,
83
83
  ReasoningStep,
84
+ // Sealed analysis context (PRJ-260)
85
+ SealedAnalysisContext,
84
86
  SimpleExecutionResult,
85
87
  SkillContext,
86
88
  SmartContextProjectState,
@@ -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 this.gatherRealContext(taskDescription, projectPath);
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.12.0",
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: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prjct-cli",
3
- "version": "1.12.0",
3
+ "version": "1.13.0",
4
4
  "description": "Context layer for AI agents. Project context for Claude Code, Gemini CLI, and more.",
5
5
  "main": "core/index.ts",
6
6
  "bin": {