prjct-cli 1.9.0 → 1.10.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 +75 -1
- package/core/__tests__/agentic/prompt-assembly.test.ts +298 -0
- package/core/__tests__/agentic/prompt-builder.test.ts +2 -2
- package/core/__tests__/agentic/token-budget.test.ts +294 -0
- package/core/agentic/anti-hallucination.ts +124 -0
- package/core/agentic/environment-block.ts +102 -0
- package/core/agentic/injection-validator.ts +16 -0
- package/core/agentic/prompt-builder.ts +339 -167
- package/core/agentic/token-budget.ts +226 -0
- package/core/services/context-selector.ts +8 -2
- package/dist/bin/prjct.mjs +435 -188
- package/package.json +1 -1
|
@@ -29,13 +29,47 @@ import type {
|
|
|
29
29
|
import { getErrorMessage, isNotFoundError } from '../types/fs'
|
|
30
30
|
import { fileExists } from '../utils/fs-helpers'
|
|
31
31
|
import { PACKAGE_ROOT } from '../utils/version'
|
|
32
|
+
import { buildAntiHallucinationBlock, type ProjectGroundTruth } from './anti-hallucination'
|
|
32
33
|
import { loadCommandContextConfig, resolveCommandContextFull } from './command-context'
|
|
34
|
+
import { buildEnvironmentBlock } from './environment-block'
|
|
33
35
|
import {
|
|
36
|
+
budgetsFromCoordinator,
|
|
34
37
|
DEFAULT_BUDGETS,
|
|
35
38
|
filterSkillsByDomains,
|
|
36
39
|
InjectionBudgetTracker,
|
|
37
40
|
truncateToTokenBudget,
|
|
38
41
|
} from './injection-validator'
|
|
42
|
+
import type { TokenBudgetCoordinator } from './token-budget'
|
|
43
|
+
|
|
44
|
+
// =============================================================================
|
|
45
|
+
// Section Priority (PRJ-301)
|
|
46
|
+
// =============================================================================
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Prompt section priorities for budget trimming.
|
|
50
|
+
* When token budget is tight, optional sections are dropped first.
|
|
51
|
+
*
|
|
52
|
+
* @see PRJ-301
|
|
53
|
+
*/
|
|
54
|
+
export type SectionPriority = 'critical' | 'important' | 'optional'
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Canonical section ordering for prompt assembly.
|
|
58
|
+
* Based on research of 25+ system prompts from Claude Code, Gemini, ChatGPT.
|
|
59
|
+
*
|
|
60
|
+
* @see PRJ-301
|
|
61
|
+
*/
|
|
62
|
+
export const PROMPT_SECTION_ORDER = [
|
|
63
|
+
'identity', // Who the model is (agent + role)
|
|
64
|
+
'environment', // Where: project, git, platform, model
|
|
65
|
+
'ground_truth', // Sealed analysis: ecosystem, stack, patterns
|
|
66
|
+
'capabilities', // Tools, agents, skills, plan mode
|
|
67
|
+
'constraints', // Anti-hallucination rules (BEFORE task context)
|
|
68
|
+
'task_context', // Files, state, memories, learned patterns
|
|
69
|
+
'task', // Template content + subtasks (the actual instructions)
|
|
70
|
+
'output_schema', // Structured response format
|
|
71
|
+
'efficiency', // Token efficiency directive
|
|
72
|
+
] as const
|
|
39
73
|
|
|
40
74
|
// Re-export types for convenience
|
|
41
75
|
export type {
|
|
@@ -81,6 +115,9 @@ class PromptBuilder {
|
|
|
81
115
|
private _templateCache: Map<string, CachedTemplate> = new Map()
|
|
82
116
|
private readonly TEMPLATE_CACHE_TTL_MS = 60_000 // 60 seconds
|
|
83
117
|
|
|
118
|
+
/** Active token budget coordinator (PRJ-266) */
|
|
119
|
+
private _coordinator: TokenBudgetCoordinator | null = null
|
|
120
|
+
|
|
84
121
|
/**
|
|
85
122
|
* Get a template with TTL caching.
|
|
86
123
|
* Returns cached content if within TTL, otherwise loads from disk.
|
|
@@ -121,6 +158,34 @@ class PromptBuilder {
|
|
|
121
158
|
this._checklistRoutingCacheTime = 0
|
|
122
159
|
}
|
|
123
160
|
|
|
161
|
+
/**
|
|
162
|
+
* Set the token budget coordinator for model-aware budget management.
|
|
163
|
+
* When set, budget allocations flow from the coordinator instead of defaults.
|
|
164
|
+
*
|
|
165
|
+
* @see PRJ-266
|
|
166
|
+
*/
|
|
167
|
+
setCoordinator(coordinator: TokenBudgetCoordinator | null): void {
|
|
168
|
+
this._coordinator = coordinator
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Get the active coordinator (may be null) */
|
|
172
|
+
getCoordinator(): TokenBudgetCoordinator | null {
|
|
173
|
+
return this._coordinator
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Get effective injection budgets.
|
|
178
|
+
* Uses coordinator allocation when available, falls back to DEFAULT_BUDGETS.
|
|
179
|
+
*
|
|
180
|
+
* @see PRJ-266
|
|
181
|
+
*/
|
|
182
|
+
private getEffectiveBudgets() {
|
|
183
|
+
if (this._coordinator) {
|
|
184
|
+
return budgetsFromCoordinator(this._coordinator)
|
|
185
|
+
}
|
|
186
|
+
return DEFAULT_BUDGETS
|
|
187
|
+
}
|
|
188
|
+
|
|
124
189
|
/**
|
|
125
190
|
* Reset context (for testing)
|
|
126
191
|
*/
|
|
@@ -293,7 +358,7 @@ class PromptBuilder {
|
|
|
293
358
|
parts.push('')
|
|
294
359
|
|
|
295
360
|
const result = parts.join('\n')
|
|
296
|
-
return truncateToTokenBudget(result,
|
|
361
|
+
return truncateToTokenBudget(result, this.getEffectiveBudgets().autoContext)
|
|
297
362
|
}
|
|
298
363
|
|
|
299
364
|
/**
|
|
@@ -390,7 +455,12 @@ class PromptBuilder {
|
|
|
390
455
|
}
|
|
391
456
|
|
|
392
457
|
/**
|
|
393
|
-
* Build a complete prompt for Claude from template, context, and enhancements
|
|
458
|
+
* Build a complete prompt for Claude from template, context, and enhancements.
|
|
459
|
+
*
|
|
460
|
+
* Section ordering follows research-backed pattern (PRJ-301):
|
|
461
|
+
* Identity → Environment → Ground Truth → Capabilities → Constraints →
|
|
462
|
+
* Task Context → Task → Output Schema → Efficiency
|
|
463
|
+
*
|
|
394
464
|
* @deprecated Use buildWithInjection for auto-injected context
|
|
395
465
|
*/
|
|
396
466
|
async build(
|
|
@@ -421,7 +491,11 @@ class PromptBuilder {
|
|
|
421
491
|
commandContext = { agents: true, patterns: true, checklist: false, modules: [] }
|
|
422
492
|
}
|
|
423
493
|
|
|
424
|
-
//
|
|
494
|
+
// =========================================================================
|
|
495
|
+
// SECTION 1: IDENTITY (critical)
|
|
496
|
+
// Tell the LLM what it is before anything else.
|
|
497
|
+
// =========================================================================
|
|
498
|
+
|
|
425
499
|
const needsAgent = commandContext.agents
|
|
426
500
|
|
|
427
501
|
if (agent && needsAgent) {
|
|
@@ -431,53 +505,100 @@ class PromptBuilder {
|
|
|
431
505
|
parts.push(`\nApply specialized expertise. Read agent file for details if needed.\n\n`)
|
|
432
506
|
}
|
|
433
507
|
|
|
434
|
-
// Core instruction (concise)
|
|
435
508
|
parts.push(`TASK: ${template.frontmatter.description}\n`)
|
|
436
509
|
|
|
437
|
-
// Tools (inline)
|
|
438
510
|
if (template.frontmatter['allowed-tools']) {
|
|
439
511
|
parts.push(`TOOLS: ${template.frontmatter['allowed-tools'].join(', ')}\n`)
|
|
440
512
|
}
|
|
441
513
|
|
|
442
|
-
// Critical parameters only
|
|
443
514
|
const params = context as { params?: { task?: string; description?: string } }
|
|
444
515
|
if (params.params?.task || params.params?.description) {
|
|
445
516
|
parts.push(`INPUT: ${params.params.task || params.params.description}\n`)
|
|
446
517
|
}
|
|
447
518
|
|
|
448
|
-
|
|
519
|
+
// =========================================================================
|
|
520
|
+
// SECTION 2: ENVIRONMENT (important)
|
|
521
|
+
// Structured env block: project, git, platform, model.
|
|
522
|
+
// =========================================================================
|
|
449
523
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
524
|
+
const projectPath = (context as { projectPath?: string }).projectPath
|
|
525
|
+
if (projectPath) {
|
|
526
|
+
const projectName = orchestratorContext?.project?.id
|
|
527
|
+
? path.basename(projectPath)
|
|
528
|
+
: path.basename(projectPath)
|
|
529
|
+
const envBlock = buildEnvironmentBlock({
|
|
530
|
+
projectName,
|
|
531
|
+
projectPath,
|
|
532
|
+
isGitRepo: true,
|
|
533
|
+
gitBranch: orchestratorContext?.realContext?.gitBranch,
|
|
534
|
+
})
|
|
535
|
+
parts.push(`\n${envBlock}\n`)
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// =========================================================================
|
|
539
|
+
// SECTION 3: GROUND TRUTH (important)
|
|
540
|
+
// Sealed analysis: ecosystem, domains, stack, code patterns.
|
|
541
|
+
// LLM knows the project reality before seeing task context.
|
|
542
|
+
// =========================================================================
|
|
453
543
|
|
|
454
|
-
// ORCHESTRATOR CONTEXT: Inject loaded agents, skills, and subtasks
|
|
455
544
|
if (orchestratorContext) {
|
|
456
|
-
parts.push('\n##
|
|
545
|
+
parts.push('\n## PROJECT ANALYSIS (Sealed)\n')
|
|
546
|
+
parts.push(`**Ecosystem**: ${orchestratorContext.project.ecosystem}\n`)
|
|
457
547
|
parts.push(`**Primary Domain**: ${orchestratorContext.primaryDomain}\n`)
|
|
458
|
-
parts.push(`**Domains**: ${orchestratorContext.detectedDomains.join(', ')}\n`)
|
|
459
|
-
|
|
548
|
+
parts.push(`**Domains**: ${orchestratorContext.detectedDomains.join(', ')}\n\n`)
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
const needsPatterns = commandContext.patterns
|
|
552
|
+
const codePatternsContent = state?.codePatterns || ''
|
|
553
|
+
if (needsPatterns && codePatternsContent && codePatternsContent.trim()) {
|
|
554
|
+
const patternSummary = this.extractPatternSummary(codePatternsContent)
|
|
555
|
+
if (patternSummary) {
|
|
556
|
+
parts.push('## CODE PATTERNS\n')
|
|
557
|
+
parts.push(patternSummary)
|
|
558
|
+
parts.push('\nFull patterns: Read analysis/patterns.md\n')
|
|
559
|
+
}
|
|
560
|
+
}
|
|
460
561
|
|
|
461
|
-
|
|
562
|
+
const analysisContent = state?.analysis || ''
|
|
563
|
+
if (needsPatterns && analysisContent && analysisContent.trim()) {
|
|
564
|
+
const stackMatch =
|
|
565
|
+
analysisContent.match(/Stack[:\s]+([^\n]+)/i) ||
|
|
566
|
+
analysisContent.match(/Technology[:\s]+([^\n]+)/i)
|
|
567
|
+
const stack = stackMatch ? stackMatch[1].trim() : 'detected'
|
|
568
|
+
|
|
569
|
+
parts.push(`\n## STACK\nStack: ${stack}\n`)
|
|
570
|
+
if (!codePatternsContent) {
|
|
571
|
+
parts.push(
|
|
572
|
+
'Read analysis/repo-summary.md + similar files before coding. Match patterns exactly.\n'
|
|
573
|
+
)
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// =========================================================================
|
|
578
|
+
// SECTION 4: CAPABILITIES (important)
|
|
579
|
+
// Available agents, skills, modules, plan mode.
|
|
580
|
+
// =========================================================================
|
|
581
|
+
|
|
582
|
+
if (orchestratorContext) {
|
|
583
|
+
// Loaded agents
|
|
462
584
|
if (orchestratorContext.agents.length > 0) {
|
|
463
|
-
parts.push('### LOADED AGENTS (Project-Specific Specialists)\n\n')
|
|
464
|
-
for (const
|
|
465
|
-
parts.push(`#### Agent: ${
|
|
466
|
-
if (
|
|
467
|
-
if (
|
|
468
|
-
if (
|
|
469
|
-
parts.push(`Skills: ${
|
|
585
|
+
parts.push('\n### LOADED AGENTS (Project-Specific Specialists)\n\n')
|
|
586
|
+
for (const orcAgent of orchestratorContext.agents) {
|
|
587
|
+
parts.push(`#### Agent: ${orcAgent.name} (${orcAgent.domain})\n`)
|
|
588
|
+
if (orcAgent.effort) parts.push(`Effort: ${orcAgent.effort}\n`)
|
|
589
|
+
if (orcAgent.model) parts.push(`Model: ${orcAgent.model}\n`)
|
|
590
|
+
if (orcAgent.skills.length > 0) {
|
|
591
|
+
parts.push(`Skills: ${orcAgent.skills.join(', ')}\n`)
|
|
470
592
|
}
|
|
471
|
-
// Truncate agent content to token budget
|
|
472
593
|
const truncatedContent = truncateToTokenBudget(
|
|
473
|
-
|
|
474
|
-
|
|
594
|
+
orcAgent.content,
|
|
595
|
+
this.getEffectiveBudgets().agentContent
|
|
475
596
|
)
|
|
476
597
|
parts.push(`\`\`\`markdown\n${truncatedContent}\n\`\`\`\n\n`)
|
|
477
598
|
}
|
|
478
599
|
}
|
|
479
600
|
|
|
480
|
-
//
|
|
601
|
+
// Loaded skills (filtered by domain)
|
|
481
602
|
const relevantSkills = filterSkillsByDomains(
|
|
482
603
|
orchestratorContext.skills,
|
|
483
604
|
orchestratorContext.detectedDomains
|
|
@@ -486,153 +607,121 @@ class PromptBuilder {
|
|
|
486
607
|
parts.push('### LOADED SKILLS (From Agent Frontmatter)\n\n')
|
|
487
608
|
for (const skill of relevantSkills) {
|
|
488
609
|
parts.push(`#### Skill: ${skill.name}\n`)
|
|
489
|
-
// Truncate skill content to token budget
|
|
490
610
|
const truncatedContent = truncateToTokenBudget(
|
|
491
611
|
skill.content,
|
|
492
|
-
|
|
612
|
+
this.getEffectiveBudgets().skillContent
|
|
493
613
|
)
|
|
494
614
|
parts.push(`\`\`\`markdown\n${truncatedContent}\n\`\`\`\n\n`)
|
|
495
615
|
}
|
|
496
616
|
}
|
|
617
|
+
}
|
|
497
618
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
if (rc.relevantFiles.length > 0) {
|
|
506
|
-
parts.push('**Relevant Files** (scored by task relevance):\n')
|
|
507
|
-
parts.push('| Score | File | Why |\n')
|
|
508
|
-
parts.push('|-------|------|-----|\n')
|
|
509
|
-
for (const f of rc.relevantFiles.slice(0, 8)) {
|
|
510
|
-
parts.push(`| ${f.score} | ${f.path} | ${f.reason} |\n`)
|
|
511
|
-
}
|
|
619
|
+
// Additional modules for SMART commands (PRJ-94/PRJ-298)
|
|
620
|
+
const additionalModules = this.getModulesForCommand(commandName, commandContext)
|
|
621
|
+
if (additionalModules.length > 0) {
|
|
622
|
+
for (const moduleName of additionalModules) {
|
|
623
|
+
const moduleContent = await this.loadModule(moduleName)
|
|
624
|
+
if (moduleContent) {
|
|
512
625
|
parts.push('\n')
|
|
626
|
+
parts.push(moduleContent)
|
|
513
627
|
}
|
|
628
|
+
}
|
|
629
|
+
}
|
|
514
630
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
631
|
+
// Plan mode / approval
|
|
632
|
+
if (planInfo?.isPlanning) {
|
|
633
|
+
parts.push(
|
|
634
|
+
`\n## PLAN MODE\nRead-only. Gather info → Analyze → Propose plan → Wait for approval.\n`
|
|
635
|
+
)
|
|
636
|
+
if (planInfo.allowedTools) parts.push(`Tools: ${planInfo.allowedTools.join(', ')}\n`)
|
|
637
|
+
}
|
|
638
|
+
if (planInfo?.requiresApproval) {
|
|
639
|
+
parts.push(
|
|
640
|
+
`\n## APPROVAL REQUIRED\nShow changes, list affected files, ask for confirmation.\n`
|
|
641
|
+
)
|
|
642
|
+
}
|
|
522
643
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
644
|
+
// =========================================================================
|
|
645
|
+
// SECTION 5: CONSTRAINTS (critical)
|
|
646
|
+
// Anti-hallucination rules BEFORE task context.
|
|
647
|
+
// LLM has constraints loaded before processing code/files.
|
|
648
|
+
// =========================================================================
|
|
649
|
+
|
|
650
|
+
if (projectPath) {
|
|
651
|
+
const groundTruth: ProjectGroundTruth = {
|
|
652
|
+
projectPath,
|
|
653
|
+
language: orchestratorContext?.project?.ecosystem,
|
|
654
|
+
techStack: orchestratorContext?.project?.conventions || [],
|
|
655
|
+
domains: this.extractDomains(state),
|
|
656
|
+
fileCount: context.files?.length || context.filteredSize || 0,
|
|
657
|
+
availableAgents: orchestratorContext?.agents?.map((a) => a.name) || [],
|
|
531
658
|
}
|
|
659
|
+
parts.push(`\n${buildAntiHallucinationBlock(groundTruth)}\n`)
|
|
660
|
+
} else {
|
|
661
|
+
// Fallback: compressed rules when no project context available
|
|
662
|
+
parts.push(this.buildCriticalRules())
|
|
663
|
+
}
|
|
532
664
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
parts.push(
|
|
552
|
-
`| ${subtask.order} | ${subtask.domain} | ${subtask.description} | ${statusIcon} |\n`
|
|
553
|
-
)
|
|
665
|
+
// =========================================================================
|
|
666
|
+
// SECTION 6: TASK CONTEXT (important)
|
|
667
|
+
// Files, codebase context, state, memories, patterns — all the data
|
|
668
|
+
// the LLM needs to work with, presented after it knows the rules.
|
|
669
|
+
// =========================================================================
|
|
670
|
+
|
|
671
|
+
// Codebase context (proactively gathered)
|
|
672
|
+
if (orchestratorContext?.realContext) {
|
|
673
|
+
const rc = orchestratorContext.realContext
|
|
674
|
+
parts.push('\n### CODEBASE CONTEXT\n\n')
|
|
675
|
+
|
|
676
|
+
parts.push(`**Git State**: Branch \`${rc.gitBranch}\` | ${rc.gitStatus}\n\n`)
|
|
677
|
+
|
|
678
|
+
if (rc.relevantFiles.length > 0) {
|
|
679
|
+
parts.push('**Relevant Files** (scored by task relevance):\n')
|
|
680
|
+
parts.push('| Score | File | Why |\n')
|
|
681
|
+
parts.push('|-------|------|-----|\n')
|
|
682
|
+
for (const f of rc.relevantFiles.slice(0, 8)) {
|
|
683
|
+
parts.push(`| ${f.score} | ${f.path} | ${f.reason} |\n`)
|
|
554
684
|
}
|
|
685
|
+
parts.push('\n')
|
|
686
|
+
}
|
|
555
687
|
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
parts.push(
|
|
560
|
-
`\n**FOCUS ON SUBTASK #${currentSubtask.order}**: ${currentSubtask.description}\n`
|
|
561
|
-
)
|
|
562
|
-
parts.push(`Agent: ${currentSubtask.agent} | Domain: ${currentSubtask.domain}\n`)
|
|
563
|
-
if (currentSubtask.dependsOn.length > 0) {
|
|
564
|
-
parts.push(`Dependencies: ${currentSubtask.dependsOn.join(', ')}\n`)
|
|
565
|
-
}
|
|
688
|
+
if (rc.signatures.length > 0) {
|
|
689
|
+
parts.push('**Code Signatures** (top files):\n')
|
|
690
|
+
for (const sig of rc.signatures) {
|
|
691
|
+
parts.push(`\`\`\`typescript\n// ${sig.path}\n${sig.content}\n\`\`\`\n`)
|
|
566
692
|
}
|
|
567
693
|
parts.push('\n')
|
|
568
694
|
}
|
|
569
|
-
}
|
|
570
695
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
696
|
+
if (rc.recentFiles.length > 0) {
|
|
697
|
+
parts.push('**Recently Changed**: ')
|
|
698
|
+
const recentSummary = rc.recentFiles
|
|
699
|
+
.slice(0, 5)
|
|
700
|
+
.map((f) => `${f.path} (${f.lastChanged})`)
|
|
701
|
+
.join(', ')
|
|
702
|
+
parts.push(`${recentSummary}\n\n`)
|
|
703
|
+
}
|
|
577
704
|
}
|
|
578
705
|
|
|
579
|
-
//
|
|
706
|
+
// File list
|
|
580
707
|
const files = context.files || []
|
|
581
708
|
if (files.length > 0) {
|
|
582
709
|
const top5 = files.slice(0, 5).join(', ')
|
|
583
710
|
parts.push(`\n## FILES: ${files.length} available. Top: ${top5}\n`)
|
|
584
711
|
parts.push('Read BEFORE modifying. Use Glob/Grep to find more.\n\n')
|
|
585
|
-
} else if (
|
|
586
|
-
parts.push(
|
|
587
|
-
`\n## PROJECT: ${(context as { projectPath: string }).projectPath}\nRead files before modifying.\n\n`
|
|
588
|
-
)
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
// OPTIMIZED: Only include patterns for code-modifying commands (config-driven, PRJ-298)
|
|
592
|
-
const needsPatterns = commandContext.patterns
|
|
593
|
-
|
|
594
|
-
// Include code patterns analysis for code-modifying commands
|
|
595
|
-
const codePatternsContent = state?.codePatterns || ''
|
|
596
|
-
if (needsPatterns && codePatternsContent && codePatternsContent.trim()) {
|
|
597
|
-
const patternSummary = this.extractPatternSummary(codePatternsContent)
|
|
598
|
-
if (patternSummary) {
|
|
599
|
-
parts.push('\n## CODE PATTERNS\n')
|
|
600
|
-
parts.push(patternSummary)
|
|
601
|
-
parts.push('\nFull patterns: Read analysis/patterns.md\n')
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
const analysisContent = state?.analysis || ''
|
|
606
|
-
if (needsPatterns && analysisContent && analysisContent.trim()) {
|
|
607
|
-
const stackMatch =
|
|
608
|
-
analysisContent.match(/Stack[:\s]+([^\n]+)/i) ||
|
|
609
|
-
analysisContent.match(/Technology[:\s]+([^\n]+)/i)
|
|
610
|
-
const stack = stackMatch ? stackMatch[1].trim() : 'detected'
|
|
611
|
-
|
|
612
|
-
parts.push(`\n## STACK\nStack: ${stack}\n`)
|
|
613
|
-
if (!codePatternsContent) {
|
|
614
|
-
parts.push(
|
|
615
|
-
'Read analysis/repo-summary.md + similar files before coding. Match patterns exactly.\n'
|
|
616
|
-
)
|
|
617
|
-
}
|
|
712
|
+
} else if (projectPath) {
|
|
713
|
+
parts.push(`\n## PROJECT: ${projectPath}\nRead files before modifying.\n\n`)
|
|
618
714
|
}
|
|
619
715
|
|
|
620
|
-
//
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
for (const moduleName of additionalModules) {
|
|
627
|
-
const moduleContent = await this.loadModule(moduleName)
|
|
628
|
-
if (moduleContent) {
|
|
629
|
-
parts.push('\n')
|
|
630
|
-
parts.push(moduleContent)
|
|
631
|
-
}
|
|
632
|
-
}
|
|
716
|
+
// Project state
|
|
717
|
+
const relevantState = this.filterRelevantState(state)
|
|
718
|
+
if (relevantState) {
|
|
719
|
+
parts.push('\n## PRJCT STATE (Project Management Data)\n')
|
|
720
|
+
parts.push(relevantState)
|
|
721
|
+
parts.push('\n')
|
|
633
722
|
}
|
|
634
723
|
|
|
635
|
-
//
|
|
724
|
+
// Learned patterns
|
|
636
725
|
if (learnedPatterns && Object.keys(learnedPatterns).some((k) => learnedPatterns[k])) {
|
|
637
726
|
parts.push('\n## PROJECT DEFAULTS (apply automatically)\n')
|
|
638
727
|
for (const [key, value] of Object.entries(learnedPatterns)) {
|
|
@@ -642,7 +731,7 @@ class PromptBuilder {
|
|
|
642
731
|
}
|
|
643
732
|
}
|
|
644
733
|
|
|
645
|
-
//
|
|
734
|
+
// Think block
|
|
646
735
|
if (thinkBlock?.plan && thinkBlock.plan.length > 0) {
|
|
647
736
|
parts.push('\n## THINK FIRST (reasoning from analysis)\n')
|
|
648
737
|
if (thinkBlock.conclusions && thinkBlock.conclusions.length > 0) {
|
|
@@ -658,7 +747,7 @@ class PromptBuilder {
|
|
|
658
747
|
parts.push(`Confidence: ${Math.round((thinkBlock.confidence || 0.5) * 100)}%\n`)
|
|
659
748
|
}
|
|
660
749
|
|
|
661
|
-
//
|
|
750
|
+
// Relevant memories
|
|
662
751
|
if (relevantMemories && relevantMemories.length > 0) {
|
|
663
752
|
parts.push('\n## CONTEXT (apply these)\n')
|
|
664
753
|
for (const memory of relevantMemories) {
|
|
@@ -669,20 +758,68 @@ class PromptBuilder {
|
|
|
669
758
|
}
|
|
670
759
|
}
|
|
671
760
|
|
|
672
|
-
|
|
673
|
-
|
|
761
|
+
parts.push('\n---\n')
|
|
762
|
+
|
|
763
|
+
// =========================================================================
|
|
764
|
+
// SECTION 7: TASK (critical)
|
|
765
|
+
// Template content (actual instructions) + subtasks.
|
|
766
|
+
// LLM reads this AFTER knowing identity, env, rules, and context.
|
|
767
|
+
// =========================================================================
|
|
768
|
+
|
|
769
|
+
parts.push(template.content)
|
|
770
|
+
|
|
771
|
+
// Subtasks (if fragmented)
|
|
772
|
+
if (orchestratorContext?.requiresFragmentation && orchestratorContext.subtasks) {
|
|
773
|
+
parts.push('\n### SUBTASKS (Execute in Order)\n\n')
|
|
674
774
|
parts.push(
|
|
675
|
-
|
|
775
|
+
'**IMPORTANT**: Focus on the CURRENT subtask. Use `p. done` when complete to advance.\n\n'
|
|
676
776
|
)
|
|
677
|
-
|
|
777
|
+
parts.push('| # | Domain | Description | Status |\n')
|
|
778
|
+
parts.push('|---|--------|-------------|--------|\n')
|
|
779
|
+
|
|
780
|
+
for (const subtask of orchestratorContext.subtasks) {
|
|
781
|
+
const statusIcon =
|
|
782
|
+
subtask.status === 'in_progress'
|
|
783
|
+
? '▶️ **CURRENT**'
|
|
784
|
+
: subtask.status === 'completed'
|
|
785
|
+
? '✅ Done'
|
|
786
|
+
: subtask.status === 'failed'
|
|
787
|
+
? '❌ Failed'
|
|
788
|
+
: '⏳ Pending'
|
|
789
|
+
parts.push(
|
|
790
|
+
`| ${subtask.order} | ${subtask.domain} | ${subtask.description} | ${statusIcon} |\n`
|
|
791
|
+
)
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
const currentSubtask = orchestratorContext.subtasks.find((s) => s.status === 'in_progress')
|
|
795
|
+
if (currentSubtask) {
|
|
796
|
+
parts.push(
|
|
797
|
+
`\n**FOCUS ON SUBTASK #${currentSubtask.order}**: ${currentSubtask.description}\n`
|
|
798
|
+
)
|
|
799
|
+
parts.push(`Agent: ${currentSubtask.agent} | Domain: ${currentSubtask.domain}\n`)
|
|
800
|
+
if (currentSubtask.dependsOn.length > 0) {
|
|
801
|
+
parts.push(`Dependencies: ${currentSubtask.dependsOn.join(', ')}\n`)
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
parts.push('\n')
|
|
678
805
|
}
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
806
|
+
|
|
807
|
+
// =========================================================================
|
|
808
|
+
// SECTION 8: OUTPUT (important)
|
|
809
|
+
// Output schema and quality checklists.
|
|
810
|
+
// =========================================================================
|
|
811
|
+
|
|
812
|
+
// Output schema (PRJ-264)
|
|
813
|
+
const schemaType = this.getSchemaTypeForCommand(commandName)
|
|
814
|
+
if (schemaType) {
|
|
815
|
+
const { renderSchemaForPrompt } = await import('../schemas/llm-output')
|
|
816
|
+
const schemaBlock = renderSchemaForPrompt(schemaType)
|
|
817
|
+
if (schemaBlock) {
|
|
818
|
+
parts.push(`\n${schemaBlock}\n`)
|
|
819
|
+
}
|
|
683
820
|
}
|
|
684
821
|
|
|
685
|
-
//
|
|
822
|
+
// Quality checklists (PRJ-298)
|
|
686
823
|
if (commandContext.checklist) {
|
|
687
824
|
const routing = await this.loadChecklistRouting()
|
|
688
825
|
const checklists = await this.loadChecklists()
|
|
@@ -698,18 +835,12 @@ class PromptBuilder {
|
|
|
698
835
|
}
|
|
699
836
|
}
|
|
700
837
|
|
|
701
|
-
//
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
const schemaBlock = renderSchemaForPrompt(schemaType)
|
|
706
|
-
if (schemaBlock) {
|
|
707
|
-
parts.push(`\n${schemaBlock}\n`)
|
|
708
|
-
}
|
|
709
|
-
}
|
|
838
|
+
// =========================================================================
|
|
839
|
+
// SECTION 9: EFFICIENCY (critical)
|
|
840
|
+
// Token efficiency directive — be concise, no preamble.
|
|
841
|
+
// =========================================================================
|
|
710
842
|
|
|
711
|
-
|
|
712
|
-
parts.push('\nEXECUTE: Follow flow. Use tools. Decide.\n')
|
|
843
|
+
parts.push(this.buildEfficiencyDirective())
|
|
713
844
|
|
|
714
845
|
return parts.join('')
|
|
715
846
|
}
|
|
@@ -721,7 +852,8 @@ class PromptBuilder {
|
|
|
721
852
|
filterRelevantState(state: State): string | null {
|
|
722
853
|
if (!state || Object.keys(state).length === 0) return null
|
|
723
854
|
|
|
724
|
-
const
|
|
855
|
+
const budgets = this.getEffectiveBudgets()
|
|
856
|
+
const tracker = new InjectionBudgetTracker({ totalPrompt: budgets.stateData })
|
|
725
857
|
const criticalFiles = ['now', 'next', 'context', 'analysis', 'codePatterns']
|
|
726
858
|
const relevant: string[] = []
|
|
727
859
|
|
|
@@ -798,7 +930,9 @@ class PromptBuilder {
|
|
|
798
930
|
}
|
|
799
931
|
|
|
800
932
|
/**
|
|
801
|
-
* Build critical anti-hallucination rules section
|
|
933
|
+
* Build critical anti-hallucination rules section.
|
|
934
|
+
* Used as fallback when full anti-hallucination block can't be built
|
|
935
|
+
* (e.g., no project path available).
|
|
802
936
|
*/
|
|
803
937
|
buildCriticalRules(): string {
|
|
804
938
|
const fileCount = this._currentContext?.files?.length || this._currentContext?.filteredSize || 0
|
|
@@ -812,6 +946,44 @@ class PromptBuilder {
|
|
|
812
946
|
Context: ${fileCount} files available. Read what you need.
|
|
813
947
|
`
|
|
814
948
|
}
|
|
949
|
+
|
|
950
|
+
/**
|
|
951
|
+
* Build token efficiency directive (PRJ-301).
|
|
952
|
+
* Instructs the LLM to be concise and avoid wasting tokens on preamble.
|
|
953
|
+
*/
|
|
954
|
+
buildEfficiencyDirective(): string {
|
|
955
|
+
return `
|
|
956
|
+
## OUTPUT RULES
|
|
957
|
+
- Be concise. Maximum 4 lines of explanation unless asked for detail.
|
|
958
|
+
- No preamble ("Here is...", "I'll help you...", "Based on...").
|
|
959
|
+
- No postamble (summaries, next steps suggestions unless asked).
|
|
960
|
+
- When executing code: show the code, not the explanation.
|
|
961
|
+
- Prefer structured output (JSON) over free text when applicable.
|
|
962
|
+
|
|
963
|
+
EXECUTE: Follow flow. Use tools. Decide.
|
|
964
|
+
`
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
/**
|
|
968
|
+
* Extract domain flags from state data.
|
|
969
|
+
* Returns the domains object if available in the raw state.
|
|
970
|
+
*/
|
|
971
|
+
private extractDomains(state: State): ProjectGroundTruth['domains'] | undefined {
|
|
972
|
+
if (!state) return undefined
|
|
973
|
+
// State may contain raw domains from state.json (loaded by context-builder)
|
|
974
|
+
const raw = state as Record<string, unknown>
|
|
975
|
+
if (raw.domains && typeof raw.domains === 'object') {
|
|
976
|
+
const d = raw.domains as Record<string, boolean>
|
|
977
|
+
return {
|
|
978
|
+
hasFrontend: d.hasFrontend ?? false,
|
|
979
|
+
hasBackend: d.hasBackend ?? false,
|
|
980
|
+
hasDatabase: d.hasDatabase ?? false,
|
|
981
|
+
hasTesting: d.hasTesting ?? false,
|
|
982
|
+
hasDocker: d.hasDocker ?? false,
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
return undefined
|
|
986
|
+
}
|
|
815
987
|
}
|
|
816
988
|
|
|
817
989
|
const promptBuilder = new PromptBuilder()
|