prjct-cli 0.56.0 → 0.57.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 +31 -0
- package/core/commands/analysis.ts +46 -1
- package/core/commands/command-data.ts +6 -1
- package/core/commands/commands.ts +7 -1
- package/core/index.ts +1 -0
- package/core/infrastructure/path-manager.ts +219 -0
- package/core/services/context-generator.ts +131 -0
- package/core/services/index.ts +3 -0
- package/core/services/nested-context-resolver.ts +378 -0
- package/core/services/sync-service.ts +2 -0
- package/dist/bin/prjct.mjs +935 -390
- package/package.json +1 -1
- package/templates/global/CLAUDE.md +25 -9
- package/templates/global/modules/CLAUDE-core.md +24 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.57.0] - 2026-02-05
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- monorepo support with nested PRJCT.md inheritance (PRJ-118) (#94)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## [0.57.0] - 2026-02-05
|
|
11
|
+
|
|
12
|
+
### Features
|
|
13
|
+
|
|
14
|
+
- **Monorepo support (PRJ-118)**: Support nested PRJCT.md files for monorepo subdirectories
|
|
15
|
+
- Detect monorepos (pnpm, npm, yarn, lerna, nx, turborepo, rush)
|
|
16
|
+
- Discover packages with workspace patterns
|
|
17
|
+
- Nested PRJCT.md inheritance (deeper files take precedence)
|
|
18
|
+
- Per-package CLAUDE.md generation with merged context
|
|
19
|
+
- `prjct sync --package=<name>` for single package sync
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- `NestedContextResolver` service for PRJCT.md discovery and inheritance
|
|
24
|
+
- `detectMonorepo()` and `discoverMonorepoPackages()` in PathManager
|
|
25
|
+
- `generateMonorepoContexts()` in ContextFileGenerator
|
|
26
|
+
|
|
27
|
+
## [0.56.1] - 2026-02-05
|
|
28
|
+
|
|
29
|
+
### Bug Fixes
|
|
30
|
+
|
|
31
|
+
- **Context injection**: Fixed template paths in CLAUDE.md - now correctly points to `~/.claude/commands/p/` instead of `templates/commands/`
|
|
32
|
+
- **Agent loading**: Added clear instructions for loading domain agents before SMART commands (task, ship, bug, done)
|
|
33
|
+
|
|
3
34
|
## [0.56.0] - 2026-02-05
|
|
4
35
|
|
|
5
36
|
### Features
|
|
@@ -227,7 +227,13 @@ export class AnalysisCommands extends PrjctCommandsBase {
|
|
|
227
227
|
*/
|
|
228
228
|
async sync(
|
|
229
229
|
projectPath: string = process.cwd(),
|
|
230
|
-
options: {
|
|
230
|
+
options: {
|
|
231
|
+
aiTools?: string[]
|
|
232
|
+
preview?: boolean
|
|
233
|
+
yes?: boolean
|
|
234
|
+
json?: boolean
|
|
235
|
+
package?: string
|
|
236
|
+
} = {}
|
|
231
237
|
): Promise<CommandResult> {
|
|
232
238
|
try {
|
|
233
239
|
const initResult = await this.ensureProjectInit(projectPath)
|
|
@@ -242,6 +248,45 @@ export class AnalysisCommands extends PrjctCommandsBase {
|
|
|
242
248
|
const globalPath = pathManager.getGlobalProjectPath(projectId)
|
|
243
249
|
const startTime = Date.now()
|
|
244
250
|
|
|
251
|
+
// Handle package-specific sync for monorepos
|
|
252
|
+
if (options.package) {
|
|
253
|
+
const monoInfo = await pathManager.detectMonorepo(projectPath)
|
|
254
|
+
if (!monoInfo.isMonorepo) {
|
|
255
|
+
return {
|
|
256
|
+
success: false,
|
|
257
|
+
error: 'Not a monorepo. --package flag only works in monorepos.',
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const pkg = monoInfo.packages.find(
|
|
262
|
+
(p) => p.name === options.package || p.relativePath === options.package
|
|
263
|
+
)
|
|
264
|
+
if (!pkg) {
|
|
265
|
+
const available = monoInfo.packages.map((p) => p.name).join(', ')
|
|
266
|
+
return {
|
|
267
|
+
success: false,
|
|
268
|
+
error: `Package "${options.package}" not found. Available: ${available}`,
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Sync only the specified package
|
|
273
|
+
const result = await syncService.sync(projectPath, {
|
|
274
|
+
aiTools: options.aiTools,
|
|
275
|
+
packagePath: pkg.path,
|
|
276
|
+
packageName: pkg.name,
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
if (options.json) {
|
|
280
|
+
console.log(
|
|
281
|
+
JSON.stringify({ success: result.success, package: pkg.name, path: pkg.relativePath })
|
|
282
|
+
)
|
|
283
|
+
} else {
|
|
284
|
+
out.done(`Synced package: ${pkg.name}`)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return { success: result.success }
|
|
288
|
+
}
|
|
289
|
+
|
|
245
290
|
// Generate diff preview if we have existing context
|
|
246
291
|
const claudeMdPath = path.join(globalPath, 'context', 'CLAUDE.md')
|
|
247
292
|
let existingContent: string | null = null
|
|
@@ -166,10 +166,15 @@ export const COMMANDS: CommandMeta[] = [
|
|
|
166
166
|
name: 'sync',
|
|
167
167
|
group: 'core',
|
|
168
168
|
description: 'Sync project state and update workflow agents',
|
|
169
|
-
usage: { claude: '/p:sync', terminal: 'prjct sync' },
|
|
169
|
+
usage: { claude: '/p:sync', terminal: 'prjct sync [--package=<name>]' },
|
|
170
170
|
implemented: true,
|
|
171
171
|
hasTemplate: true,
|
|
172
172
|
requiresProject: true,
|
|
173
|
+
features: [
|
|
174
|
+
'Monorepo support: --package=<name> for single package sync',
|
|
175
|
+
'Nested PRJCT.md inheritance',
|
|
176
|
+
'Per-package CLAUDE.md generation',
|
|
177
|
+
],
|
|
173
178
|
},
|
|
174
179
|
{
|
|
175
180
|
name: 'suggest',
|
|
@@ -184,7 +184,13 @@ class PrjctCommands {
|
|
|
184
184
|
|
|
185
185
|
async sync(
|
|
186
186
|
projectPath: string = process.cwd(),
|
|
187
|
-
options: {
|
|
187
|
+
options: {
|
|
188
|
+
aiTools?: string[]
|
|
189
|
+
preview?: boolean
|
|
190
|
+
yes?: boolean
|
|
191
|
+
json?: boolean
|
|
192
|
+
package?: string
|
|
193
|
+
} = {}
|
|
188
194
|
): Promise<CommandResult> {
|
|
189
195
|
return this.analysis.sync(projectPath, options)
|
|
190
196
|
}
|
package/core/index.ts
CHANGED
|
@@ -129,6 +129,7 @@ async function main(): Promise<void> {
|
|
|
129
129
|
preview: options.preview === true,
|
|
130
130
|
yes: options.yes === true,
|
|
131
131
|
json: options.json === true,
|
|
132
|
+
package: options.package ? String(options.package) : undefined,
|
|
132
133
|
}),
|
|
133
134
|
start: () => commands.start(),
|
|
134
135
|
// Context (for Claude templates)
|
|
@@ -13,10 +13,28 @@ import crypto from 'node:crypto'
|
|
|
13
13
|
import fs from 'node:fs/promises'
|
|
14
14
|
import os from 'node:os'
|
|
15
15
|
import path from 'node:path'
|
|
16
|
+
import { globSync } from 'glob'
|
|
16
17
|
import type { SessionInfo } from '../types'
|
|
17
18
|
import * as dateHelper from '../utils/date-helper'
|
|
18
19
|
import * as fileHelper from '../utils/file-helper'
|
|
19
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Monorepo detection result
|
|
23
|
+
*/
|
|
24
|
+
export interface MonorepoInfo {
|
|
25
|
+
isMonorepo: boolean
|
|
26
|
+
type: 'pnpm' | 'npm' | 'yarn' | 'lerna' | 'nx' | 'rush' | 'turborepo' | null
|
|
27
|
+
rootPath: string
|
|
28
|
+
packages: MonorepoPackage[]
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface MonorepoPackage {
|
|
32
|
+
name: string
|
|
33
|
+
path: string
|
|
34
|
+
relativePath: string
|
|
35
|
+
hasPrjctMd: boolean
|
|
36
|
+
}
|
|
37
|
+
|
|
20
38
|
class PathManager {
|
|
21
39
|
globalBaseDir: string
|
|
22
40
|
globalProjectsDir: string
|
|
@@ -360,6 +378,207 @@ class PathManager {
|
|
|
360
378
|
getContextPath(projectId: string): string {
|
|
361
379
|
return path.join(this.getGlobalProjectPath(projectId), 'context')
|
|
362
380
|
}
|
|
381
|
+
|
|
382
|
+
// ===========================================================================
|
|
383
|
+
// Monorepo Detection
|
|
384
|
+
// ===========================================================================
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Detect if a project is a monorepo and get package information
|
|
388
|
+
*/
|
|
389
|
+
async detectMonorepo(projectPath: string): Promise<MonorepoInfo> {
|
|
390
|
+
const result: MonorepoInfo = {
|
|
391
|
+
isMonorepo: false,
|
|
392
|
+
type: null,
|
|
393
|
+
rootPath: projectPath,
|
|
394
|
+
packages: [],
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Check for various monorepo configurations
|
|
398
|
+
const checks = [
|
|
399
|
+
{ file: 'pnpm-workspace.yaml', type: 'pnpm' as const },
|
|
400
|
+
{ file: 'lerna.json', type: 'lerna' as const },
|
|
401
|
+
{ file: 'nx.json', type: 'nx' as const },
|
|
402
|
+
{ file: 'rush.json', type: 'rush' as const },
|
|
403
|
+
{ file: 'turbo.json', type: 'turborepo' as const },
|
|
404
|
+
]
|
|
405
|
+
|
|
406
|
+
for (const check of checks) {
|
|
407
|
+
const filePath = path.join(projectPath, check.file)
|
|
408
|
+
if (await fileHelper.fileExists(filePath)) {
|
|
409
|
+
result.isMonorepo = true
|
|
410
|
+
result.type = check.type
|
|
411
|
+
break
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Check package.json for workspaces (npm/yarn)
|
|
416
|
+
if (!result.isMonorepo) {
|
|
417
|
+
const packageJsonPath = path.join(projectPath, 'package.json')
|
|
418
|
+
if (await fileHelper.fileExists(packageJsonPath)) {
|
|
419
|
+
try {
|
|
420
|
+
const content = await fs.readFile(packageJsonPath, 'utf-8')
|
|
421
|
+
const pkg = JSON.parse(content)
|
|
422
|
+
if (pkg.workspaces) {
|
|
423
|
+
result.isMonorepo = true
|
|
424
|
+
result.type = 'npm' // Could be yarn too, but npm is more generic
|
|
425
|
+
}
|
|
426
|
+
} catch {
|
|
427
|
+
// Invalid package.json, ignore
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// If it's a monorepo, discover packages
|
|
433
|
+
if (result.isMonorepo) {
|
|
434
|
+
result.packages = await this.discoverMonorepoPackages(projectPath, result.type)
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
return result
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Discover all packages in a monorepo
|
|
442
|
+
*/
|
|
443
|
+
async discoverMonorepoPackages(
|
|
444
|
+
rootPath: string,
|
|
445
|
+
type: MonorepoInfo['type']
|
|
446
|
+
): Promise<MonorepoPackage[]> {
|
|
447
|
+
const packages: MonorepoPackage[] = []
|
|
448
|
+
let patterns: string[] = []
|
|
449
|
+
|
|
450
|
+
try {
|
|
451
|
+
if (type === 'pnpm') {
|
|
452
|
+
// Read pnpm-workspace.yaml
|
|
453
|
+
const yaml = await fs.readFile(path.join(rootPath, 'pnpm-workspace.yaml'), 'utf-8')
|
|
454
|
+
// Simple YAML parsing for packages array
|
|
455
|
+
const match = yaml.match(/packages:\s*\n((?:\s*-\s*.+\n?)+)/)
|
|
456
|
+
if (match) {
|
|
457
|
+
patterns = match[1]
|
|
458
|
+
.split('\n')
|
|
459
|
+
.map((line) => line.replace(/^\s*-\s*['"]?|['"]?\s*$/g, ''))
|
|
460
|
+
.filter(Boolean)
|
|
461
|
+
}
|
|
462
|
+
} else if (type === 'npm' || type === 'lerna') {
|
|
463
|
+
// Read from package.json workspaces or lerna.json
|
|
464
|
+
const packageJsonPath = path.join(rootPath, 'package.json')
|
|
465
|
+
const content = await fs.readFile(packageJsonPath, 'utf-8')
|
|
466
|
+
const pkg = JSON.parse(content)
|
|
467
|
+
if (Array.isArray(pkg.workspaces)) {
|
|
468
|
+
patterns = pkg.workspaces
|
|
469
|
+
} else if (pkg.workspaces?.packages) {
|
|
470
|
+
patterns = pkg.workspaces.packages
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Also check lerna.json
|
|
474
|
+
if (type === 'lerna') {
|
|
475
|
+
const lernaPath = path.join(rootPath, 'lerna.json')
|
|
476
|
+
if (await fileHelper.fileExists(lernaPath)) {
|
|
477
|
+
const lernaContent = await fs.readFile(lernaPath, 'utf-8')
|
|
478
|
+
const lerna = JSON.parse(lernaContent)
|
|
479
|
+
if (lerna.packages) {
|
|
480
|
+
patterns = lerna.packages
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
} else if (type === 'nx') {
|
|
485
|
+
// NX uses apps/* and libs/* by default
|
|
486
|
+
patterns = ['apps/*', 'libs/*', 'packages/*']
|
|
487
|
+
} else if (type === 'turborepo') {
|
|
488
|
+
// Turborepo reads from package.json workspaces
|
|
489
|
+
const packageJsonPath = path.join(rootPath, 'package.json')
|
|
490
|
+
const content = await fs.readFile(packageJsonPath, 'utf-8')
|
|
491
|
+
const pkg = JSON.parse(content)
|
|
492
|
+
if (Array.isArray(pkg.workspaces)) {
|
|
493
|
+
patterns = pkg.workspaces
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// If no patterns found, use common defaults
|
|
498
|
+
if (patterns.length === 0) {
|
|
499
|
+
patterns = ['packages/*', 'apps/*', 'libs/*']
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// Expand glob patterns to find packages
|
|
503
|
+
for (const pattern of patterns) {
|
|
504
|
+
// Skip negation patterns for now
|
|
505
|
+
if (pattern.startsWith('!')) continue
|
|
506
|
+
|
|
507
|
+
const matches = globSync(pattern, {
|
|
508
|
+
cwd: rootPath,
|
|
509
|
+
absolute: false,
|
|
510
|
+
})
|
|
511
|
+
|
|
512
|
+
for (const match of matches) {
|
|
513
|
+
const packagePath = path.join(rootPath, match)
|
|
514
|
+
const packageJsonPath = path.join(packagePath, 'package.json')
|
|
515
|
+
|
|
516
|
+
// Only include directories with package.json
|
|
517
|
+
if (await fileHelper.fileExists(packageJsonPath)) {
|
|
518
|
+
try {
|
|
519
|
+
const content = await fs.readFile(packageJsonPath, 'utf-8')
|
|
520
|
+
const pkg = JSON.parse(content)
|
|
521
|
+
const prjctMdPath = path.join(packagePath, 'PRJCT.md')
|
|
522
|
+
|
|
523
|
+
packages.push({
|
|
524
|
+
name: pkg.name || path.basename(match),
|
|
525
|
+
path: packagePath,
|
|
526
|
+
relativePath: match,
|
|
527
|
+
hasPrjctMd: await fileHelper.fileExists(prjctMdPath),
|
|
528
|
+
})
|
|
529
|
+
} catch {
|
|
530
|
+
// Invalid package.json, skip
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
} catch {
|
|
536
|
+
// Error reading monorepo config, return empty
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
return packages
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Check if current path is within a monorepo package
|
|
544
|
+
* Returns the package info if found, null otherwise
|
|
545
|
+
*/
|
|
546
|
+
async findContainingPackage(
|
|
547
|
+
currentPath: string,
|
|
548
|
+
monoInfo: MonorepoInfo
|
|
549
|
+
): Promise<MonorepoPackage | null> {
|
|
550
|
+
if (!monoInfo.isMonorepo) return null
|
|
551
|
+
|
|
552
|
+
const normalizedCurrent = path.resolve(currentPath)
|
|
553
|
+
|
|
554
|
+
for (const pkg of monoInfo.packages) {
|
|
555
|
+
const normalizedPkg = path.resolve(pkg.path)
|
|
556
|
+
if (normalizedCurrent.startsWith(normalizedPkg)) {
|
|
557
|
+
return pkg
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
return null
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* Find monorepo root from any subdirectory
|
|
566
|
+
* Walks up the directory tree looking for monorepo markers
|
|
567
|
+
*/
|
|
568
|
+
async findMonorepoRoot(startPath: string): Promise<string | null> {
|
|
569
|
+
let currentPath = path.resolve(startPath)
|
|
570
|
+
const root = path.parse(currentPath).root
|
|
571
|
+
|
|
572
|
+
while (currentPath !== root) {
|
|
573
|
+
const monoInfo = await this.detectMonorepo(currentPath)
|
|
574
|
+
if (monoInfo.isMonorepo) {
|
|
575
|
+
return currentPath
|
|
576
|
+
}
|
|
577
|
+
currentPath = path.dirname(currentPath)
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
return null
|
|
581
|
+
}
|
|
363
582
|
}
|
|
364
583
|
|
|
365
584
|
const pathManager = new PathManager()
|
|
@@ -11,8 +11,10 @@
|
|
|
11
11
|
|
|
12
12
|
import fs from 'node:fs/promises'
|
|
13
13
|
import path from 'node:path'
|
|
14
|
+
import pathManager from '../infrastructure/path-manager'
|
|
14
15
|
import dateHelper from '../utils/date-helper'
|
|
15
16
|
import { mergePreservedSections, validatePreserveBlocks } from '../utils/preserve-sections'
|
|
17
|
+
import { NestedContextResolver } from './nested-context-resolver'
|
|
16
18
|
|
|
17
19
|
// ============================================================================
|
|
18
20
|
// TYPES
|
|
@@ -325,6 +327,135 @@ ${
|
|
|
325
327
|
|
|
326
328
|
await this.writeWithPreservation(path.join(contextPath, 'shipped.md'), content)
|
|
327
329
|
}
|
|
330
|
+
|
|
331
|
+
// ==========================================================================
|
|
332
|
+
// MONOREPO SUPPORT
|
|
333
|
+
// ==========================================================================
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Generate CLAUDE.md files for each package in a monorepo
|
|
337
|
+
* Each package gets its own context file with inherited + package-specific rules
|
|
338
|
+
*/
|
|
339
|
+
async generateMonorepoContexts(
|
|
340
|
+
git: GitData,
|
|
341
|
+
stats: ProjectStats,
|
|
342
|
+
commands: Commands,
|
|
343
|
+
agents: AgentInfo[]
|
|
344
|
+
): Promise<string[]> {
|
|
345
|
+
const monoInfo = await pathManager.detectMonorepo(this.config.projectPath)
|
|
346
|
+
|
|
347
|
+
if (!monoInfo.isMonorepo) {
|
|
348
|
+
return []
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const generatedFiles: string[] = []
|
|
352
|
+
const resolver = new NestedContextResolver(this.config.projectPath)
|
|
353
|
+
await resolver.initialize()
|
|
354
|
+
|
|
355
|
+
// Generate CLAUDE.md for each package that has PRJCT.md
|
|
356
|
+
for (const pkg of monoInfo.packages) {
|
|
357
|
+
if (!pkg.hasPrjctMd) continue
|
|
358
|
+
|
|
359
|
+
const resolvedCtx = await resolver.getPackageContext(pkg.name)
|
|
360
|
+
if (!resolvedCtx) continue
|
|
361
|
+
|
|
362
|
+
const content = await this.generatePackageClaudeMd(
|
|
363
|
+
pkg,
|
|
364
|
+
resolvedCtx,
|
|
365
|
+
git,
|
|
366
|
+
stats,
|
|
367
|
+
commands,
|
|
368
|
+
agents
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
// Write to the package directory
|
|
372
|
+
const claudePath = path.join(pkg.path, 'CLAUDE.md')
|
|
373
|
+
await this.writeWithPreservation(claudePath, content)
|
|
374
|
+
generatedFiles.push(path.relative(this.config.projectPath, claudePath))
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
return generatedFiles
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Generate CLAUDE.md content for a specific package
|
|
382
|
+
*/
|
|
383
|
+
private async generatePackageClaudeMd(
|
|
384
|
+
pkg: { name: string; path: string; relativePath: string },
|
|
385
|
+
resolvedCtx: { content: string; sources: string[]; overrides: string[] },
|
|
386
|
+
git: GitData,
|
|
387
|
+
stats: ProjectStats,
|
|
388
|
+
commands: Commands,
|
|
389
|
+
agents: AgentInfo[]
|
|
390
|
+
): Promise<string> {
|
|
391
|
+
const workflowAgents = agents.filter((a) => a.type === 'workflow').map((a) => a.name)
|
|
392
|
+
const domainAgents = agents.filter((a) => a.type === 'domain').map((a) => a.name)
|
|
393
|
+
|
|
394
|
+
// Try to read package-specific info
|
|
395
|
+
let pkgVersion = stats.version
|
|
396
|
+
let pkgName = pkg.name
|
|
397
|
+
try {
|
|
398
|
+
const pkgJsonPath = path.join(pkg.path, 'package.json')
|
|
399
|
+
const pkgJson = JSON.parse(await fs.readFile(pkgJsonPath, 'utf-8'))
|
|
400
|
+
pkgVersion = pkgJson.version || stats.version
|
|
401
|
+
pkgName = pkgJson.name || pkg.name
|
|
402
|
+
} catch {
|
|
403
|
+
// Use defaults
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return `# ${pkgName} - Package Rules
|
|
407
|
+
<!-- package: ${pkg.relativePath} -->
|
|
408
|
+
<!-- monorepo: ${stats.name} -->
|
|
409
|
+
<!-- Generated: ${dateHelper.getTimestamp()} -->
|
|
410
|
+
<!-- Sources: ${resolvedCtx.sources.join(' → ')} -->
|
|
411
|
+
|
|
412
|
+
## THIS PACKAGE
|
|
413
|
+
|
|
414
|
+
**Name:** ${pkgName}
|
|
415
|
+
**Path:** ${pkg.relativePath}
|
|
416
|
+
**Version:** ${pkgVersion}
|
|
417
|
+
**Monorepo:** ${stats.name}
|
|
418
|
+
|
|
419
|
+
---
|
|
420
|
+
|
|
421
|
+
## INHERITED CONTEXT
|
|
422
|
+
|
|
423
|
+
${resolvedCtx.content || '_No PRJCT.md rules defined_'}
|
|
424
|
+
|
|
425
|
+
${resolvedCtx.overrides.length > 0 ? `\n**Overrides:** ${resolvedCtx.overrides.join(', ')}\n` : ''}
|
|
426
|
+
|
|
427
|
+
---
|
|
428
|
+
|
|
429
|
+
## COMMANDS
|
|
430
|
+
|
|
431
|
+
| Action | Command |
|
|
432
|
+
|--------|---------|
|
|
433
|
+
| Install | \`${commands.install}\` |
|
|
434
|
+
| Dev | \`${commands.dev}\` |
|
|
435
|
+
| Test | \`${commands.test}\` |
|
|
436
|
+
| Build | \`${commands.build}\` |
|
|
437
|
+
|
|
438
|
+
---
|
|
439
|
+
|
|
440
|
+
## PROJECT STATE
|
|
441
|
+
|
|
442
|
+
| Field | Value |
|
|
443
|
+
|-------|-------|
|
|
444
|
+
| Package | ${pkgName} |
|
|
445
|
+
| Monorepo | ${stats.name} |
|
|
446
|
+
| Branch | ${git.branch} |
|
|
447
|
+
| Ecosystem | ${stats.ecosystem} |
|
|
448
|
+
|
|
449
|
+
---
|
|
450
|
+
|
|
451
|
+
## AGENTS
|
|
452
|
+
|
|
453
|
+
Load from \`~/.prjct-cli/projects/${this.config.projectId}/agents/\`:
|
|
454
|
+
|
|
455
|
+
**Workflow**: ${workflowAgents.join(', ')}
|
|
456
|
+
**Domain**: ${domainAgents.join(', ') || 'none'}
|
|
457
|
+
`
|
|
458
|
+
}
|
|
328
459
|
}
|
|
329
460
|
|
|
330
461
|
export default ContextFileGenerator
|
package/core/services/index.ts
CHANGED
|
@@ -32,6 +32,9 @@ export type { GitData } from './git-analyzer'
|
|
|
32
32
|
// Git Analyzer - Extracted from sync-service (PRJ-85)
|
|
33
33
|
export { GitAnalyzer, getEmptyGitData, gitAnalyzer } from './git-analyzer'
|
|
34
34
|
export { MemoryService, memoryService } from './memory-service'
|
|
35
|
+
export type { ContextSection, NestedContext, ResolvedContext } from './nested-context-resolver'
|
|
36
|
+
// Nested Context Resolver - Monorepo PRJCT.md inheritance (PRJ-118)
|
|
37
|
+
export { NestedContextResolver } from './nested-context-resolver'
|
|
35
38
|
export type { IndexOptions, RelevantContext, ScanResult } from './project-index'
|
|
36
39
|
// Project Index - Persistent scanning with scoring
|
|
37
40
|
export { createProjectIndexer, ProjectIndexer, RELEVANCE_THRESHOLD } from './project-index'
|