prjct-cli 1.11.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 +87 -2354
- package/core/__tests__/agentic/analysis-injection.test.ts +377 -0
- package/core/__tests__/storage/subtask-handoff.test.ts +237 -0
- package/core/agentic/anti-hallucination.ts +23 -1
- package/core/agentic/orchestrator-executor.ts +36 -3
- package/core/agentic/prompt-builder.ts +64 -1
- package/core/schemas/state.ts +22 -2
- package/core/storage/state-storage.ts +40 -8
- package/core/types/agentic.ts +38 -0
- package/core/types/index.ts +2 -0
- package/dist/bin/prjct.mjs +162 -12
- package/package.json +1 -1
- package/templates/commands/done.md +86 -18
|
@@ -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 {
|
|
@@ -800,6 +845,24 @@ class PromptBuilder {
|
|
|
800
845
|
if (currentSubtask.dependsOn.length > 0) {
|
|
801
846
|
parts.push(`Dependencies: ${currentSubtask.dependsOn.join(', ')}\n`)
|
|
802
847
|
}
|
|
848
|
+
|
|
849
|
+
// Inject previous subtask handoff for context continuity (PRJ-262)
|
|
850
|
+
if (currentSubtask.handoff) {
|
|
851
|
+
const h = currentSubtask.handoff
|
|
852
|
+
parts.push('\n### Previous Subtask Handoff\n\n')
|
|
853
|
+
parts.push(`**From:** ${h.fromSubtask}\n\n`)
|
|
854
|
+
parts.push('**What was done:**\n')
|
|
855
|
+
for (const item of h.whatWasDone) {
|
|
856
|
+
parts.push(`- ${item}\n`)
|
|
857
|
+
}
|
|
858
|
+
if (h.filesChanged.length > 0) {
|
|
859
|
+
parts.push('\n**Files changed:**\n')
|
|
860
|
+
for (const f of h.filesChanged) {
|
|
861
|
+
parts.push(`- \`${f.path}\` (${f.action})\n`)
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
parts.push(`\n**Context for this subtask:**\n${h.outputForNextAgent}\n`)
|
|
865
|
+
}
|
|
803
866
|
}
|
|
804
867
|
parts.push('\n')
|
|
805
868
|
}
|
package/core/schemas/state.ts
CHANGED
|
@@ -44,11 +44,18 @@ export const SubtaskSummarySchema = z.object({
|
|
|
44
44
|
action: z.enum(['created', 'modified', 'deleted']),
|
|
45
45
|
})
|
|
46
46
|
),
|
|
47
|
-
whatWasDone: z.array(z.string()),
|
|
48
|
-
outputForNextAgent: z.string().
|
|
47
|
+
whatWasDone: z.array(z.string()).min(1),
|
|
48
|
+
outputForNextAgent: z.string().min(1),
|
|
49
49
|
notes: z.string().optional(),
|
|
50
50
|
})
|
|
51
51
|
|
|
52
|
+
// Schema for validating completion data before persisting
|
|
53
|
+
// Used by completeSubtask() to enforce mandatory handoff
|
|
54
|
+
export const SubtaskCompletionDataSchema = z.object({
|
|
55
|
+
output: z.string().min(1, 'Subtask output is required'),
|
|
56
|
+
summary: SubtaskSummarySchema,
|
|
57
|
+
})
|
|
58
|
+
|
|
52
59
|
// Subtask schema for task fragmentation
|
|
53
60
|
export const SubtaskSchema = z.object({
|
|
54
61
|
id: z.string(), // subtask-xxx
|
|
@@ -169,6 +176,7 @@ export type ActivityType = z.infer<typeof ActivityTypeSchema>
|
|
|
169
176
|
|
|
170
177
|
export type Subtask = z.infer<typeof SubtaskSchema>
|
|
171
178
|
export type SubtaskSummary = z.infer<typeof SubtaskSummarySchema>
|
|
179
|
+
export type SubtaskCompletionData = z.infer<typeof SubtaskCompletionDataSchema>
|
|
172
180
|
export type SubtaskProgress = z.infer<typeof SubtaskProgressSchema>
|
|
173
181
|
|
|
174
182
|
export type CurrentTask = z.infer<typeof CurrentTaskSchema>
|
|
@@ -197,6 +205,18 @@ export const parseQueue = (data: unknown): QueueJson => QueueJsonSchema.parse(da
|
|
|
197
205
|
export const safeParseState = (data: unknown) => StateJsonSchema.safeParse(data)
|
|
198
206
|
export const safeParseQueue = (data: unknown) => QueueJsonSchema.safeParse(data)
|
|
199
207
|
|
|
208
|
+
/** Validate subtask completion data — returns errors or null */
|
|
209
|
+
export const validateSubtaskCompletion = (
|
|
210
|
+
data: unknown
|
|
211
|
+
): { success: true; data: SubtaskCompletionData } | { success: false; errors: string[] } => {
|
|
212
|
+
const result = SubtaskCompletionDataSchema.safeParse(data)
|
|
213
|
+
if (result.success) return { success: true, data: result.data }
|
|
214
|
+
return {
|
|
215
|
+
success: false,
|
|
216
|
+
errors: result.error.issues.map((i) => `${i.path.join('.')}: ${i.message}`),
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
200
220
|
// =============================================================================
|
|
201
221
|
// Defaults
|
|
202
222
|
// =============================================================================
|
|
@@ -14,9 +14,9 @@ import type {
|
|
|
14
14
|
PreviousTask,
|
|
15
15
|
StateJson,
|
|
16
16
|
Subtask,
|
|
17
|
-
|
|
17
|
+
SubtaskCompletionData,
|
|
18
18
|
} from '../schemas/state'
|
|
19
|
-
import { StateJsonSchema } from '../schemas/state'
|
|
19
|
+
import { StateJsonSchema, SubtaskCompletionDataSchema } from '../schemas/state'
|
|
20
20
|
import { getTimestamp, toRelative } from '../utils/date-helper'
|
|
21
21
|
import { md } from '../utils/markdown-builder'
|
|
22
22
|
import type { WorkflowCommand } from '../workflow/state-machine'
|
|
@@ -497,14 +497,23 @@ class StateStorage extends StorageManager<StateJson> {
|
|
|
497
497
|
}
|
|
498
498
|
|
|
499
499
|
/**
|
|
500
|
-
* Complete current subtask and advance to next
|
|
501
|
-
*
|
|
500
|
+
* Complete current subtask and advance to next.
|
|
501
|
+
* Requires output and summary for mandatory handoff (PRJ-262).
|
|
502
|
+
* Returns the next subtask (or null if all complete).
|
|
502
503
|
*/
|
|
503
504
|
async completeSubtask(
|
|
504
505
|
projectId: string,
|
|
505
|
-
|
|
506
|
-
summary?: SubtaskSummary
|
|
506
|
+
completionData: SubtaskCompletionData
|
|
507
507
|
): Promise<Subtask | null> {
|
|
508
|
+
// Validate handoff data with Zod before persisting
|
|
509
|
+
const validation = SubtaskCompletionDataSchema.safeParse(completionData)
|
|
510
|
+
if (!validation.success) {
|
|
511
|
+
const errors = validation.error.issues.map((i) => `${i.path.join('.')}: ${i.message}`)
|
|
512
|
+
throw new Error(`Subtask completion requires handoff data:\n${errors.join('\n')}`)
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
const { output, summary } = validation.data
|
|
516
|
+
|
|
508
517
|
const state = await this.read(projectId)
|
|
509
518
|
if (!state.currentTask?.subtasks) return null
|
|
510
519
|
|
|
@@ -512,7 +521,7 @@ class StateStorage extends StorageManager<StateJson> {
|
|
|
512
521
|
const current = state.currentTask.subtasks[currentIndex]
|
|
513
522
|
if (!current) return null
|
|
514
523
|
|
|
515
|
-
// Mark current as completed
|
|
524
|
+
// Mark current as completed with mandatory handoff
|
|
516
525
|
const updatedSubtasks = [...state.currentTask.subtasks]
|
|
517
526
|
updatedSubtasks[currentIndex] = {
|
|
518
527
|
...current,
|
|
@@ -548,12 +557,14 @@ class StateStorage extends StorageManager<StateJson> {
|
|
|
548
557
|
lastUpdated: getTimestamp(),
|
|
549
558
|
}))
|
|
550
559
|
|
|
551
|
-
// Publish event
|
|
560
|
+
// Publish event with handoff data
|
|
552
561
|
await this.publishEvent(projectId, 'subtask.completed', {
|
|
553
562
|
taskId: state.currentTask.id,
|
|
554
563
|
subtaskId: current.id,
|
|
555
564
|
description: current.description,
|
|
556
565
|
output,
|
|
566
|
+
handoff: summary.outputForNextAgent,
|
|
567
|
+
filesChanged: summary.filesChanged.length,
|
|
557
568
|
progress: { completed, total, percentage },
|
|
558
569
|
})
|
|
559
570
|
|
|
@@ -592,6 +603,27 @@ class StateStorage extends StorageManager<StateJson> {
|
|
|
592
603
|
return state.currentTask.subtasks[prevIndex] || null
|
|
593
604
|
}
|
|
594
605
|
|
|
606
|
+
/**
|
|
607
|
+
* Get handoff from the most recently completed subtask (PRJ-262).
|
|
608
|
+
* Returns the summary.outputForNextAgent and related context,
|
|
609
|
+
* or null if no previous subtask or no handoff data.
|
|
610
|
+
*/
|
|
611
|
+
async getPreviousHandoff(projectId: string): Promise<{
|
|
612
|
+
fromSubtask: string
|
|
613
|
+
outputForNextAgent: string
|
|
614
|
+
filesChanged: Array<{ path: string; action: string }>
|
|
615
|
+
whatWasDone: string[]
|
|
616
|
+
} | null> {
|
|
617
|
+
const prev = await this.getPreviousSubtask(projectId)
|
|
618
|
+
if (!prev?.summary?.outputForNextAgent) return null
|
|
619
|
+
return {
|
|
620
|
+
fromSubtask: prev.description,
|
|
621
|
+
outputForNextAgent: prev.summary.outputForNextAgent,
|
|
622
|
+
filesChanged: prev.summary.filesChanged,
|
|
623
|
+
whatWasDone: prev.summary.whatWasDone,
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
595
627
|
/**
|
|
596
628
|
* Get all subtasks
|
|
597
629
|
*/
|
package/core/types/agentic.ts
CHANGED
|
@@ -673,6 +673,13 @@ export interface OrchestratorSubtask {
|
|
|
673
673
|
status: 'pending' | 'in_progress' | 'completed' | 'failed'
|
|
674
674
|
dependsOn: string[]
|
|
675
675
|
order: number
|
|
676
|
+
/** Handoff from previous subtask — injected into prompt for context continuity (PRJ-262) */
|
|
677
|
+
handoff?: {
|
|
678
|
+
fromSubtask: string
|
|
679
|
+
outputForNextAgent: string
|
|
680
|
+
filesChanged: Array<{ path: string; action: string }>
|
|
681
|
+
whatWasDone: string[]
|
|
682
|
+
}
|
|
676
683
|
}
|
|
677
684
|
|
|
678
685
|
/**
|
|
@@ -717,4 +724,35 @@ export interface OrchestratorContext {
|
|
|
717
724
|
}
|
|
718
725
|
/** Real codebase context gathered proactively */
|
|
719
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
|
|
720
758
|
}
|
package/core/types/index.ts
CHANGED
package/dist/bin/prjct.mjs
CHANGED
|
@@ -10212,7 +10212,7 @@ var init_shipped = __esm({
|
|
|
10212
10212
|
|
|
10213
10213
|
// core/schemas/state.ts
|
|
10214
10214
|
import { z as z14 } from "zod";
|
|
10215
|
-
var PrioritySchema, TaskTypeSchema, TaskSectionSchema, TaskStatusSchema, ActivityTypeSchema, SubtaskSummarySchema, SubtaskSchema, SubtaskProgressSchema, CurrentTaskSchema, PreviousTaskSchema, StateJsonSchema, QueueTaskSchema, QueueJsonSchema, StatsSchema, RecentActivitySchema, StateSchemaFull;
|
|
10215
|
+
var PrioritySchema, TaskTypeSchema, TaskSectionSchema, TaskStatusSchema, ActivityTypeSchema, SubtaskSummarySchema, SubtaskCompletionDataSchema, SubtaskSchema, SubtaskProgressSchema, CurrentTaskSchema, PreviousTaskSchema, StateJsonSchema, QueueTaskSchema, QueueJsonSchema, StatsSchema, RecentActivitySchema, StateSchemaFull;
|
|
10216
10216
|
var init_state = __esm({
|
|
10217
10217
|
"core/schemas/state.ts"() {
|
|
10218
10218
|
"use strict";
|
|
@@ -10244,10 +10244,14 @@ var init_state = __esm({
|
|
|
10244
10244
|
action: z14.enum(["created", "modified", "deleted"])
|
|
10245
10245
|
})
|
|
10246
10246
|
),
|
|
10247
|
-
whatWasDone: z14.array(z14.string()),
|
|
10248
|
-
outputForNextAgent: z14.string().
|
|
10247
|
+
whatWasDone: z14.array(z14.string()).min(1),
|
|
10248
|
+
outputForNextAgent: z14.string().min(1),
|
|
10249
10249
|
notes: z14.string().optional()
|
|
10250
10250
|
});
|
|
10251
|
+
SubtaskCompletionDataSchema = z14.object({
|
|
10252
|
+
output: z14.string().min(1, "Subtask output is required"),
|
|
10253
|
+
summary: SubtaskSummarySchema
|
|
10254
|
+
});
|
|
10251
10255
|
SubtaskSchema = z14.object({
|
|
10252
10256
|
id: z14.string(),
|
|
10253
10257
|
// subtask-xxx
|
|
@@ -13259,10 +13263,18 @@ var init_state_storage = __esm({
|
|
|
13259
13263
|
});
|
|
13260
13264
|
}
|
|
13261
13265
|
/**
|
|
13262
|
-
* Complete current subtask and advance to next
|
|
13263
|
-
*
|
|
13266
|
+
* Complete current subtask and advance to next.
|
|
13267
|
+
* Requires output and summary for mandatory handoff (PRJ-262).
|
|
13268
|
+
* Returns the next subtask (or null if all complete).
|
|
13264
13269
|
*/
|
|
13265
|
-
async completeSubtask(projectId,
|
|
13270
|
+
async completeSubtask(projectId, completionData) {
|
|
13271
|
+
const validation = SubtaskCompletionDataSchema.safeParse(completionData);
|
|
13272
|
+
if (!validation.success) {
|
|
13273
|
+
const errors = validation.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`);
|
|
13274
|
+
throw new Error(`Subtask completion requires handoff data:
|
|
13275
|
+
${errors.join("\n")}`);
|
|
13276
|
+
}
|
|
13277
|
+
const { output, summary } = validation.data;
|
|
13266
13278
|
const state = await this.read(projectId);
|
|
13267
13279
|
if (!state.currentTask?.subtasks) return null;
|
|
13268
13280
|
const currentIndex = state.currentTask.currentSubtaskIndex || 0;
|
|
@@ -13302,6 +13314,8 @@ var init_state_storage = __esm({
|
|
|
13302
13314
|
subtaskId: current.id,
|
|
13303
13315
|
description: current.description,
|
|
13304
13316
|
output,
|
|
13317
|
+
handoff: summary.outputForNextAgent,
|
|
13318
|
+
filesChanged: summary.filesChanged.length,
|
|
13305
13319
|
progress: { completed, total, percentage }
|
|
13306
13320
|
});
|
|
13307
13321
|
return nextIndex < total ? updatedSubtasks[nextIndex] : null;
|
|
@@ -13334,6 +13348,21 @@ var init_state_storage = __esm({
|
|
|
13334
13348
|
if (prevIndex < 0) return null;
|
|
13335
13349
|
return state.currentTask.subtasks[prevIndex] || null;
|
|
13336
13350
|
}
|
|
13351
|
+
/**
|
|
13352
|
+
* Get handoff from the most recently completed subtask (PRJ-262).
|
|
13353
|
+
* Returns the summary.outputForNextAgent and related context,
|
|
13354
|
+
* or null if no previous subtask or no handoff data.
|
|
13355
|
+
*/
|
|
13356
|
+
async getPreviousHandoff(projectId) {
|
|
13357
|
+
const prev = await this.getPreviousSubtask(projectId);
|
|
13358
|
+
if (!prev?.summary?.outputForNextAgent) return null;
|
|
13359
|
+
return {
|
|
13360
|
+
fromSubtask: prev.description,
|
|
13361
|
+
outputForNextAgent: prev.summary.outputForNextAgent,
|
|
13362
|
+
filesChanged: prev.summary.filesChanged,
|
|
13363
|
+
whatWasDone: prev.summary.whatWasDone
|
|
13364
|
+
};
|
|
13365
|
+
}
|
|
13337
13366
|
/**
|
|
13338
13367
|
* Get all subtasks
|
|
13339
13368
|
*/
|
|
@@ -14174,7 +14203,10 @@ var init_orchestrator_executor = __esm({
|
|
|
14174
14203
|
const { domains, primary } = await this.detectDomains(taskDescription, projectId, repoAnalysis);
|
|
14175
14204
|
const agents = await this.loadAgents(domains, projectId);
|
|
14176
14205
|
const skills = await this.loadSkills(agents);
|
|
14177
|
-
const realContext = await
|
|
14206
|
+
const [realContext, sealedAnalysis] = await Promise.all([
|
|
14207
|
+
this.gatherRealContext(taskDescription, projectPath),
|
|
14208
|
+
this.loadSealedAnalysis(projectId)
|
|
14209
|
+
]);
|
|
14178
14210
|
const requiresFragmentation = this.shouldFragment(domains, taskDescription);
|
|
14179
14211
|
let subtasks = null;
|
|
14180
14212
|
if (requiresFragmentation && command === "task") {
|
|
@@ -14192,7 +14224,8 @@ var init_orchestrator_executor = __esm({
|
|
|
14192
14224
|
ecosystem: repoAnalysis?.ecosystem || "unknown",
|
|
14193
14225
|
conventions: repoAnalysis?.conventions || []
|
|
14194
14226
|
},
|
|
14195
|
-
realContext
|
|
14227
|
+
realContext,
|
|
14228
|
+
sealedAnalysis
|
|
14196
14229
|
};
|
|
14197
14230
|
}
|
|
14198
14231
|
/**
|
|
@@ -14272,6 +14305,31 @@ var init_orchestrator_executor = __esm({
|
|
|
14272
14305
|
return { branch: "unknown", status: "git unavailable" };
|
|
14273
14306
|
}
|
|
14274
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
|
+
}
|
|
14275
14333
|
/**
|
|
14276
14334
|
* Load repo-analysis.json for project context
|
|
14277
14335
|
*/
|
|
@@ -15352,9 +15410,24 @@ function buildAntiHallucinationBlock(truth) {
|
|
|
15352
15410
|
if (truth.framework) available.push(truth.framework);
|
|
15353
15411
|
const techStack = truth.techStack ?? [];
|
|
15354
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
|
+
}
|
|
15355
15425
|
if (available.length > 0) {
|
|
15356
15426
|
parts.push(`AVAILABLE in this project: ${available.join(", ")}`);
|
|
15357
15427
|
}
|
|
15428
|
+
if (truth.analysisPackageManager) {
|
|
15429
|
+
parts.push(`PACKAGE MANAGER: ${truth.analysisPackageManager}`);
|
|
15430
|
+
}
|
|
15358
15431
|
if (truth.domains) {
|
|
15359
15432
|
const absent = Object.entries(truth.domains).filter(([, hasIt]) => !hasIt).map(([key]) => DOMAIN_LABELS[key]).filter(Boolean);
|
|
15360
15433
|
if (absent.length > 0) {
|
|
@@ -15401,7 +15474,13 @@ var init_anti_hallucination = __esm({
|
|
|
15401
15474
|
/** Total files in project */
|
|
15402
15475
|
fileCount: z15.number().optional(),
|
|
15403
15476
|
/** Available agent names (e.g., ['backend', 'testing']) */
|
|
15404
|
-
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()
|
|
15405
15484
|
});
|
|
15406
15485
|
DOMAIN_LABELS = {
|
|
15407
15486
|
hasFrontend: "Frontend (UI/components)",
|
|
@@ -16145,14 +16224,57 @@ ${envBlock}
|
|
|
16145
16224
|
`);
|
|
16146
16225
|
}
|
|
16147
16226
|
if (orchestratorContext) {
|
|
16227
|
+
const sa = orchestratorContext.sealedAnalysis;
|
|
16148
16228
|
parts.push("\n## PROJECT ANALYSIS (Sealed)\n");
|
|
16149
16229
|
parts.push(`**Ecosystem**: ${orchestratorContext.project.ecosystem}
|
|
16150
16230
|
`);
|
|
16151
16231
|
parts.push(`**Primary Domain**: ${orchestratorContext.primaryDomain}
|
|
16152
16232
|
`);
|
|
16153
16233
|
parts.push(`**Domains**: ${orchestratorContext.detectedDomains.join(", ")}
|
|
16154
|
-
|
|
16155
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");
|
|
16156
16278
|
}
|
|
16157
16279
|
const needsPatterns = commandContext.patterns;
|
|
16158
16280
|
const codePatternsContent = state?.codePatterns || "";
|
|
@@ -16253,13 +16375,18 @@ Show changes, list affected files, ask for confirmation.
|
|
|
16253
16375
|
);
|
|
16254
16376
|
}
|
|
16255
16377
|
if (projectPath) {
|
|
16378
|
+
const sa = orchestratorContext?.sealedAnalysis;
|
|
16256
16379
|
const groundTruth2 = {
|
|
16257
16380
|
projectPath,
|
|
16258
16381
|
language: orchestratorContext?.project?.ecosystem,
|
|
16259
16382
|
techStack: orchestratorContext?.project?.conventions || [],
|
|
16260
16383
|
domains: this.extractDomains(state),
|
|
16261
16384
|
fileCount: context2.files?.length || context2.filteredSize || 0,
|
|
16262
|
-
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
|
|
16263
16390
|
};
|
|
16264
16391
|
parts.push(`
|
|
16265
16392
|
${buildAntiHallucinationBlock(groundTruth2)}
|
|
@@ -16386,6 +16513,29 @@ Read files before modifying.
|
|
|
16386
16513
|
`);
|
|
16387
16514
|
if (currentSubtask.dependsOn.length > 0) {
|
|
16388
16515
|
parts.push(`Dependencies: ${currentSubtask.dependsOn.join(", ")}
|
|
16516
|
+
`);
|
|
16517
|
+
}
|
|
16518
|
+
if (currentSubtask.handoff) {
|
|
16519
|
+
const h = currentSubtask.handoff;
|
|
16520
|
+
parts.push("\n### Previous Subtask Handoff\n\n");
|
|
16521
|
+
parts.push(`**From:** ${h.fromSubtask}
|
|
16522
|
+
|
|
16523
|
+
`);
|
|
16524
|
+
parts.push("**What was done:**\n");
|
|
16525
|
+
for (const item of h.whatWasDone) {
|
|
16526
|
+
parts.push(`- ${item}
|
|
16527
|
+
`);
|
|
16528
|
+
}
|
|
16529
|
+
if (h.filesChanged.length > 0) {
|
|
16530
|
+
parts.push("\n**Files changed:**\n");
|
|
16531
|
+
for (const f of h.filesChanged) {
|
|
16532
|
+
parts.push(`- \`${f.path}\` (${f.action})
|
|
16533
|
+
`);
|
|
16534
|
+
}
|
|
16535
|
+
}
|
|
16536
|
+
parts.push(`
|
|
16537
|
+
**Context for this subtask:**
|
|
16538
|
+
${h.outputForNextAgent}
|
|
16389
16539
|
`);
|
|
16390
16540
|
}
|
|
16391
16541
|
}
|
|
@@ -31050,7 +31200,7 @@ var require_package = __commonJS({
|
|
|
31050
31200
|
"package.json"(exports, module) {
|
|
31051
31201
|
module.exports = {
|
|
31052
31202
|
name: "prjct-cli",
|
|
31053
|
-
version: "1.
|
|
31203
|
+
version: "1.13.0",
|
|
31054
31204
|
description: "Context layer for AI agents. Project context for Claude Code, Gemini CLI, and more.",
|
|
31055
31205
|
main: "core/index.ts",
|
|
31056
31206
|
bin: {
|