prjct-cli 0.54.2 → 0.55.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,39 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.55.0] - 2026-01-30
4
+
5
+ ### Features
6
+
7
+ - modular CLAUDE.md for reduced token usage - PRJ-94 (#86)
8
+
9
+
10
+ ## [0.54.4] - 2026-01-30
11
+
12
+ ### Improved
13
+
14
+ - **Modular CLAUDE.md for reduced token usage** (PRJ-94)
15
+ - Split global template into 5 modules: core, git, storage, commands, intelligence
16
+ - Added profile-based composition: minimal (84% reduction), standard (56%), full (23%)
17
+ - FAST commands (sync, next, dash) use minimal profile (~394 tokens)
18
+ - SMART commands (task, ship, bug) dynamically inject additional modules
19
+ - Target 40-60% token reduction achieved for simple commands
20
+
21
+ ## [0.54.3] - 2026-01-30
22
+
23
+ ### Bug Fixes
24
+
25
+ - standardize confirmation pattern across all commands (#85)
26
+
27
+
28
+ ## [0.54.3] - 2026-01-30
29
+
30
+ ### Fixed
31
+
32
+ - Standardize confirmation pattern across all commands to use AskUserQuestion
33
+ - Updated `ship.md`, `merge.md`, `git.md`, `task.md` templates
34
+ - Replaces inconsistent "Proceed? (yes/no)" text prompts
35
+ - All confirmations now use consistent options: "Yes (Recommended)", "No, cancel"
36
+
3
37
  ## [0.54.2] - 2026-01-30
4
38
 
5
39
  ### Bug Fixes
@@ -26,6 +26,7 @@ import type {
26
26
  ThinkBlock,
27
27
  } from '../types'
28
28
  import { isNotFoundError } from '../types/fs'
29
+ import { getPackageRoot } from '../utils/version'
29
30
 
30
31
  // Re-export types for convenience
31
32
  export type {
@@ -125,6 +126,31 @@ class PromptBuilder {
125
126
  this._currentContext = context
126
127
  }
127
128
 
129
+ /**
130
+ * Load a specific CLAUDE module for SMART commands (PRJ-94)
131
+ * These modules extend the base global CLAUDE.md for complex operations
132
+ */
133
+ loadModule(moduleName: string): string | null {
134
+ const modulePath = path.join(getPackageRoot(), 'templates/global/modules', moduleName)
135
+ return this.getTemplate(modulePath)
136
+ }
137
+
138
+ /**
139
+ * Get additional modules needed for SMART commands (PRJ-94)
140
+ * Returns array of module names that should be injected
141
+ */
142
+ getModulesForCommand(commandName: string): string[] {
143
+ const smartCommands: Record<string, string[]> = {
144
+ task: ['CLAUDE-intelligence.md', 'CLAUDE-storage.md'],
145
+ ship: ['CLAUDE-intelligence.md', 'CLAUDE-storage.md'],
146
+ bug: ['CLAUDE-intelligence.md'],
147
+ done: ['CLAUDE-storage.md'],
148
+ work: ['CLAUDE-intelligence.md', 'CLAUDE-storage.md'],
149
+ spec: ['CLAUDE-intelligence.md'],
150
+ }
151
+ return smartCommands[commandName] || []
152
+ }
153
+
128
154
  /**
129
155
  * Load quality checklists from templates/checklists/
130
156
  * Uses lazy loading with TTL cache.
@@ -561,6 +587,18 @@ class PromptBuilder {
561
587
  // CRITICAL: Compressed rules
562
588
  parts.push(this.buildCriticalRules())
563
589
 
590
+ // PRJ-94: Inject additional modules for SMART commands
591
+ const additionalModules = this.getModulesForCommand(commandName)
592
+ if (additionalModules.length > 0) {
593
+ for (const moduleName of additionalModules) {
594
+ const moduleContent = this.loadModule(moduleName)
595
+ if (moduleContent) {
596
+ parts.push('\n')
597
+ parts.push(moduleContent)
598
+ }
599
+ }
600
+ }
601
+
564
602
  // P1.1: Learned Patterns
565
603
  if (learnedPatterns && Object.keys(learnedPatterns).some((k) => learnedPatterns[k])) {
566
604
  parts.push('\n## PROJECT DEFAULTS (apply automatically)\n')
@@ -25,6 +25,101 @@ import type {
25
25
  import { isNotFoundError } from '../types/fs'
26
26
  import { getPackageRoot } from '../utils/version'
27
27
 
28
+ // =============================================================================
29
+ // Module Types
30
+ // =============================================================================
31
+
32
+ interface ModuleProfile {
33
+ description: string
34
+ modules: string[]
35
+ }
36
+
37
+ interface ModuleConfig {
38
+ description: string
39
+ version: string
40
+ profiles: Record<string, ModuleProfile>
41
+ default: string
42
+ commandProfiles: Record<string, string>
43
+ }
44
+
45
+ // =============================================================================
46
+ // Modular Template Composition (PRJ-94)
47
+ // =============================================================================
48
+
49
+ /**
50
+ * Load module configuration
51
+ */
52
+ async function loadModuleConfig(): Promise<ModuleConfig | null> {
53
+ try {
54
+ const configPath = path.join(getPackageRoot(), 'templates/global/modules/module-config.json')
55
+ const content = await fs.readFile(configPath, 'utf-8')
56
+ return JSON.parse(content) as ModuleConfig
57
+ } catch {
58
+ return null
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Compose global template from modules based on profile
64
+ * @param profile - Profile name ('full', 'standard', 'minimal') or null for default
65
+ * @returns Composed template content with markers
66
+ */
67
+ export async function composeGlobalTemplate(profile?: string): Promise<string> {
68
+ const config = await loadModuleConfig()
69
+ const modulesDir = path.join(getPackageRoot(), 'templates/global/modules')
70
+
71
+ // Fallback to legacy template if config not found
72
+ if (!config) {
73
+ const legacyPath = path.join(getPackageRoot(), 'templates/global/CLAUDE.md')
74
+ return fs.readFile(legacyPath, 'utf-8')
75
+ }
76
+
77
+ const profileName = profile || config.default
78
+ const selectedProfile = config.profiles[profileName]
79
+
80
+ if (!selectedProfile) {
81
+ // Fallback to default profile
82
+ const defaultProfile = config.profiles[config.default]
83
+ if (!defaultProfile) {
84
+ const legacyPath = path.join(getPackageRoot(), 'templates/global/CLAUDE.md')
85
+ return fs.readFile(legacyPath, 'utf-8')
86
+ }
87
+ }
88
+
89
+ const modules = (selectedProfile || config.profiles[config.default]).modules
90
+
91
+ // Load and compose modules
92
+ const parts: string[] = []
93
+ parts.push('<!-- prjct:start - DO NOT REMOVE THIS MARKER -->')
94
+
95
+ for (const moduleName of modules) {
96
+ try {
97
+ const modulePath = path.join(modulesDir, moduleName)
98
+ const content = await fs.readFile(modulePath, 'utf-8')
99
+ parts.push('')
100
+ parts.push(content)
101
+ } catch {
102
+ // Module not found, skip
103
+ console.warn(`Module not found: ${moduleName}`)
104
+ }
105
+ }
106
+
107
+ parts.push('')
108
+ parts.push('<!-- prjct:end - DO NOT REMOVE THIS MARKER -->')
109
+ parts.push('')
110
+
111
+ return parts.join('\n')
112
+ }
113
+
114
+ /**
115
+ * Get recommended profile for a command
116
+ */
117
+ export async function getProfileForCommand(command: string): Promise<string> {
118
+ const config = await loadModuleConfig()
119
+ if (!config) return 'standard'
120
+ return config.commandProfiles[command] || config.default
121
+ }
122
+
28
123
  // =============================================================================
29
124
  // Global Config
30
125
  // =============================================================================
@@ -89,17 +184,29 @@ export async function installGlobalConfig(): Promise<GlobalConfigResult> {
89
184
  activeProvider.contextFile
90
185
  )
91
186
 
92
- // Read template content
187
+ // Read template content - use modular composition (PRJ-94)
93
188
  let templateContent = ''
94
189
  try {
190
+ // First try provider-specific template
95
191
  templateContent = await fs.readFile(templatePath, 'utf-8')
96
192
  } catch (_error) {
97
- // Fallback if provider-specific template not found
98
- const fallbackTemplatePath = path.join(getPackageRoot(), 'templates/global/CLAUDE.md')
99
- templateContent = await fs.readFile(fallbackTemplatePath, 'utf-8')
100
- // If it is Gemini, we should rename Claude to Gemini in the fallback content
101
- if (providerName === 'gemini') {
102
- templateContent = templateContent.replace(/Claude/g, 'Gemini')
193
+ // Use modular composition for Claude (PRJ-94)
194
+ if (providerName === 'claude') {
195
+ try {
196
+ templateContent = await composeGlobalTemplate('standard')
197
+ } catch {
198
+ // Final fallback to legacy template
199
+ const fallbackTemplatePath = path.join(getPackageRoot(), 'templates/global/CLAUDE.md')
200
+ templateContent = await fs.readFile(fallbackTemplatePath, 'utf-8')
201
+ }
202
+ } else {
203
+ // Fallback for other providers
204
+ const fallbackTemplatePath = path.join(getPackageRoot(), 'templates/global/CLAUDE.md')
205
+ templateContent = await fs.readFile(fallbackTemplatePath, 'utf-8')
206
+ // If it is Gemini, we should rename Claude to Gemini in the fallback content
207
+ if (providerName === 'gemini') {
208
+ templateContent = templateContent.replace(/Claude/g, 'Gemini')
209
+ }
103
210
  }
104
211
  }
105
212
 
@@ -5525,6 +5525,49 @@ var init_doctor_service = __esm({
5525
5525
  import fs16 from "node:fs/promises";
5526
5526
  import os5 from "node:os";
5527
5527
  import path16 from "node:path";
5528
+ async function loadModuleConfig() {
5529
+ try {
5530
+ const configPath = path16.join(getPackageRoot(), "templates/global/modules/module-config.json");
5531
+ const content = await fs16.readFile(configPath, "utf-8");
5532
+ return JSON.parse(content);
5533
+ } catch {
5534
+ return null;
5535
+ }
5536
+ }
5537
+ async function composeGlobalTemplate(profile) {
5538
+ const config = await loadModuleConfig();
5539
+ const modulesDir = path16.join(getPackageRoot(), "templates/global/modules");
5540
+ if (!config) {
5541
+ const legacyPath = path16.join(getPackageRoot(), "templates/global/CLAUDE.md");
5542
+ return fs16.readFile(legacyPath, "utf-8");
5543
+ }
5544
+ const profileName = profile || config.default;
5545
+ const selectedProfile = config.profiles[profileName];
5546
+ if (!selectedProfile) {
5547
+ const defaultProfile = config.profiles[config.default];
5548
+ if (!defaultProfile) {
5549
+ const legacyPath = path16.join(getPackageRoot(), "templates/global/CLAUDE.md");
5550
+ return fs16.readFile(legacyPath, "utf-8");
5551
+ }
5552
+ }
5553
+ const modules = (selectedProfile || config.profiles[config.default]).modules;
5554
+ const parts = [];
5555
+ parts.push("<!-- prjct:start - DO NOT REMOVE THIS MARKER -->");
5556
+ for (const moduleName of modules) {
5557
+ try {
5558
+ const modulePath = path16.join(modulesDir, moduleName);
5559
+ const content = await fs16.readFile(modulePath, "utf-8");
5560
+ parts.push("");
5561
+ parts.push(content);
5562
+ } catch {
5563
+ console.warn(`Module not found: ${moduleName}`);
5564
+ }
5565
+ }
5566
+ parts.push("");
5567
+ parts.push("<!-- prjct:end - DO NOT REMOVE THIS MARKER -->");
5568
+ parts.push("");
5569
+ return parts.join("\n");
5570
+ }
5528
5571
  async function installDocs() {
5529
5572
  try {
5530
5573
  const docsDir = path16.join(os5.homedir(), ".prjct-cli", "docs");
@@ -5569,10 +5612,19 @@ async function installGlobalConfig2() {
5569
5612
  try {
5570
5613
  templateContent = await fs16.readFile(templatePath, "utf-8");
5571
5614
  } catch (_error) {
5572
- const fallbackTemplatePath = path16.join(getPackageRoot(), "templates/global/CLAUDE.md");
5573
- templateContent = await fs16.readFile(fallbackTemplatePath, "utf-8");
5574
- if (providerName === "gemini") {
5575
- templateContent = templateContent.replace(/Claude/g, "Gemini");
5615
+ if (providerName === "claude") {
5616
+ try {
5617
+ templateContent = await composeGlobalTemplate("standard");
5618
+ } catch {
5619
+ const fallbackTemplatePath = path16.join(getPackageRoot(), "templates/global/CLAUDE.md");
5620
+ templateContent = await fs16.readFile(fallbackTemplatePath, "utf-8");
5621
+ }
5622
+ } else {
5623
+ const fallbackTemplatePath = path16.join(getPackageRoot(), "templates/global/CLAUDE.md");
5624
+ templateContent = await fs16.readFile(fallbackTemplatePath, "utf-8");
5625
+ if (providerName === "gemini") {
5626
+ templateContent = templateContent.replace(/Claude/g, "Gemini");
5627
+ }
5576
5628
  }
5577
5629
  }
5578
5630
  let existingContent = "";
@@ -5655,6 +5707,8 @@ var init_command_installer = __esm({
5655
5707
  "use strict";
5656
5708
  init_fs();
5657
5709
  init_version();
5710
+ __name(loadModuleConfig, "loadModuleConfig");
5711
+ __name(composeGlobalTemplate, "composeGlobalTemplate");
5658
5712
  __name(installDocs, "installDocs");
5659
5713
  __name(installGlobalConfig2, "installGlobalConfig");
5660
5714
  CommandInstaller = class {
@@ -12510,6 +12564,7 @@ var init_prompt_builder = __esm({
12510
12564
  init_outcomes2();
12511
12565
  init_storage2();
12512
12566
  init_fs();
12567
+ init_version();
12513
12568
  PromptBuilder = class {
12514
12569
  static {
12515
12570
  __name(this, "PromptBuilder");
@@ -12572,6 +12627,29 @@ var init_prompt_builder = __esm({
12572
12627
  setContext(context2) {
12573
12628
  this._currentContext = context2;
12574
12629
  }
12630
+ /**
12631
+ * Load a specific CLAUDE module for SMART commands (PRJ-94)
12632
+ * These modules extend the base global CLAUDE.md for complex operations
12633
+ */
12634
+ loadModule(moduleName) {
12635
+ const modulePath = path25.join(getPackageRoot(), "templates/global/modules", moduleName);
12636
+ return this.getTemplate(modulePath);
12637
+ }
12638
+ /**
12639
+ * Get additional modules needed for SMART commands (PRJ-94)
12640
+ * Returns array of module names that should be injected
12641
+ */
12642
+ getModulesForCommand(commandName) {
12643
+ const smartCommands = {
12644
+ task: ["CLAUDE-intelligence.md", "CLAUDE-storage.md"],
12645
+ ship: ["CLAUDE-intelligence.md", "CLAUDE-storage.md"],
12646
+ bug: ["CLAUDE-intelligence.md"],
12647
+ done: ["CLAUDE-storage.md"],
12648
+ work: ["CLAUDE-intelligence.md", "CLAUDE-storage.md"],
12649
+ spec: ["CLAUDE-intelligence.md"]
12650
+ };
12651
+ return smartCommands[commandName] || [];
12652
+ }
12575
12653
  /**
12576
12654
  * Load quality checklists from templates/checklists/
12577
12655
  * Uses lazy loading with TTL cache.
@@ -12926,6 +13004,16 @@ Stack: ${stack}
12926
13004
  }
12927
13005
  }
12928
13006
  parts.push(this.buildCriticalRules());
13007
+ const additionalModules = this.getModulesForCommand(commandName);
13008
+ if (additionalModules.length > 0) {
13009
+ for (const moduleName of additionalModules) {
13010
+ const moduleContent = this.loadModule(moduleName);
13011
+ if (moduleContent) {
13012
+ parts.push("\n");
13013
+ parts.push(moduleContent);
13014
+ }
13015
+ }
13016
+ }
12929
13017
  if (learnedPatterns && Object.keys(learnedPatterns).some((k) => learnedPatterns[k])) {
12930
13018
  parts.push("\n## PROJECT DEFAULTS (apply automatically)\n");
12931
13019
  for (const [key, value] of Object.entries(learnedPatterns)) {
@@ -25073,7 +25161,7 @@ var require_package = __commonJS({
25073
25161
  "package.json"(exports, module) {
25074
25162
  module.exports = {
25075
25163
  name: "prjct-cli",
25076
- version: "0.54.2",
25164
+ version: "0.55.0",
25077
25165
  description: "Context layer for AI agents. Project context for Claude Code, Gemini CLI, and more.",
25078
25166
  main: "core/index.ts",
25079
25167
  bin: {
@@ -393,7 +393,9 @@ var init_ai_provider = __esm({
393
393
  var command_installer_exports = {};
394
394
  __export(command_installer_exports, {
395
395
  CommandInstaller: () => CommandInstaller,
396
+ composeGlobalTemplate: () => composeGlobalTemplate,
396
397
  default: () => command_installer_default,
398
+ getProfileForCommand: () => getProfileForCommand,
397
399
  getProviderPaths: () => getProviderPaths,
398
400
  installDocs: () => installDocs,
399
401
  installGlobalConfig: () => installGlobalConfig,
@@ -459,6 +461,57 @@ var VERSION = getVersion();
459
461
  var PACKAGE_ROOT = getPackageRoot();
460
462
 
461
463
  // core/infrastructure/command-installer.ts
464
+ async function loadModuleConfig() {
465
+ try {
466
+ const configPath = import_node_path3.default.join(getPackageRoot(), "templates/global/modules/module-config.json");
467
+ const content = await import_promises.default.readFile(configPath, "utf-8");
468
+ return JSON.parse(content);
469
+ } catch {
470
+ return null;
471
+ }
472
+ }
473
+ __name(loadModuleConfig, "loadModuleConfig");
474
+ async function composeGlobalTemplate(profile) {
475
+ const config = await loadModuleConfig();
476
+ const modulesDir = import_node_path3.default.join(getPackageRoot(), "templates/global/modules");
477
+ if (!config) {
478
+ const legacyPath = import_node_path3.default.join(getPackageRoot(), "templates/global/CLAUDE.md");
479
+ return import_promises.default.readFile(legacyPath, "utf-8");
480
+ }
481
+ const profileName = profile || config.default;
482
+ const selectedProfile = config.profiles[profileName];
483
+ if (!selectedProfile) {
484
+ const defaultProfile = config.profiles[config.default];
485
+ if (!defaultProfile) {
486
+ const legacyPath = import_node_path3.default.join(getPackageRoot(), "templates/global/CLAUDE.md");
487
+ return import_promises.default.readFile(legacyPath, "utf-8");
488
+ }
489
+ }
490
+ const modules = (selectedProfile || config.profiles[config.default]).modules;
491
+ const parts = [];
492
+ parts.push("<!-- prjct:start - DO NOT REMOVE THIS MARKER -->");
493
+ for (const moduleName of modules) {
494
+ try {
495
+ const modulePath = import_node_path3.default.join(modulesDir, moduleName);
496
+ const content = await import_promises.default.readFile(modulePath, "utf-8");
497
+ parts.push("");
498
+ parts.push(content);
499
+ } catch {
500
+ console.warn(`Module not found: ${moduleName}`);
501
+ }
502
+ }
503
+ parts.push("");
504
+ parts.push("<!-- prjct:end - DO NOT REMOVE THIS MARKER -->");
505
+ parts.push("");
506
+ return parts.join("\n");
507
+ }
508
+ __name(composeGlobalTemplate, "composeGlobalTemplate");
509
+ async function getProfileForCommand(command) {
510
+ const config = await loadModuleConfig();
511
+ if (!config) return "standard";
512
+ return config.commandProfiles[command] || config.default;
513
+ }
514
+ __name(getProfileForCommand, "getProfileForCommand");
462
515
  async function installDocs() {
463
516
  try {
464
517
  const docsDir = import_node_path3.default.join(import_node_os2.default.homedir(), ".prjct-cli", "docs");
@@ -504,10 +557,19 @@ async function installGlobalConfig() {
504
557
  try {
505
558
  templateContent = await import_promises.default.readFile(templatePath, "utf-8");
506
559
  } catch (_error) {
507
- const fallbackTemplatePath = import_node_path3.default.join(getPackageRoot(), "templates/global/CLAUDE.md");
508
- templateContent = await import_promises.default.readFile(fallbackTemplatePath, "utf-8");
509
- if (providerName === "gemini") {
510
- templateContent = templateContent.replace(/Claude/g, "Gemini");
560
+ if (providerName === "claude") {
561
+ try {
562
+ templateContent = await composeGlobalTemplate("standard");
563
+ } catch {
564
+ const fallbackTemplatePath = import_node_path3.default.join(getPackageRoot(), "templates/global/CLAUDE.md");
565
+ templateContent = await import_promises.default.readFile(fallbackTemplatePath, "utf-8");
566
+ }
567
+ } else {
568
+ const fallbackTemplatePath = import_node_path3.default.join(getPackageRoot(), "templates/global/CLAUDE.md");
569
+ templateContent = await import_promises.default.readFile(fallbackTemplatePath, "utf-8");
570
+ if (providerName === "gemini") {
571
+ templateContent = templateContent.replace(/Claude/g, "Gemini");
572
+ }
511
573
  }
512
574
  }
513
575
  let existingContent = "";
@@ -919,6 +981,8 @@ var command_installer_default = commandInstaller;
919
981
  // Annotate the CommonJS export names for ESM import in node:
920
982
  0 && (module.exports = {
921
983
  CommandInstaller,
984
+ composeGlobalTemplate,
985
+ getProfileForCommand,
922
986
  getProviderPaths,
923
987
  installDocs,
924
988
  installGlobalConfig,
@@ -470,6 +470,51 @@ init_ai_provider();
470
470
  var import_promises = __toESM(require("node:fs/promises"));
471
471
  var import_node_os2 = __toESM(require("node:os"));
472
472
  var import_node_path3 = __toESM(require("node:path"));
473
+ async function loadModuleConfig() {
474
+ try {
475
+ const configPath = import_node_path3.default.join(getPackageRoot(), "templates/global/modules/module-config.json");
476
+ const content = await import_promises.default.readFile(configPath, "utf-8");
477
+ return JSON.parse(content);
478
+ } catch {
479
+ return null;
480
+ }
481
+ }
482
+ __name(loadModuleConfig, "loadModuleConfig");
483
+ async function composeGlobalTemplate(profile) {
484
+ const config = await loadModuleConfig();
485
+ const modulesDir = import_node_path3.default.join(getPackageRoot(), "templates/global/modules");
486
+ if (!config) {
487
+ const legacyPath = import_node_path3.default.join(getPackageRoot(), "templates/global/CLAUDE.md");
488
+ return import_promises.default.readFile(legacyPath, "utf-8");
489
+ }
490
+ const profileName = profile || config.default;
491
+ const selectedProfile = config.profiles[profileName];
492
+ if (!selectedProfile) {
493
+ const defaultProfile = config.profiles[config.default];
494
+ if (!defaultProfile) {
495
+ const legacyPath = import_node_path3.default.join(getPackageRoot(), "templates/global/CLAUDE.md");
496
+ return import_promises.default.readFile(legacyPath, "utf-8");
497
+ }
498
+ }
499
+ const modules = (selectedProfile || config.profiles[config.default]).modules;
500
+ const parts = [];
501
+ parts.push("<!-- prjct:start - DO NOT REMOVE THIS MARKER -->");
502
+ for (const moduleName of modules) {
503
+ try {
504
+ const modulePath = import_node_path3.default.join(modulesDir, moduleName);
505
+ const content = await import_promises.default.readFile(modulePath, "utf-8");
506
+ parts.push("");
507
+ parts.push(content);
508
+ } catch {
509
+ console.warn(`Module not found: ${moduleName}`);
510
+ }
511
+ }
512
+ parts.push("");
513
+ parts.push("<!-- prjct:end - DO NOT REMOVE THIS MARKER -->");
514
+ parts.push("");
515
+ return parts.join("\n");
516
+ }
517
+ __name(composeGlobalTemplate, "composeGlobalTemplate");
473
518
  async function installDocs() {
474
519
  try {
475
520
  const docsDir = import_node_path3.default.join(import_node_os2.default.homedir(), ".prjct-cli", "docs");
@@ -515,10 +560,19 @@ async function installGlobalConfig() {
515
560
  try {
516
561
  templateContent = await import_promises.default.readFile(templatePath, "utf-8");
517
562
  } catch (_error) {
518
- const fallbackTemplatePath = import_node_path3.default.join(getPackageRoot(), "templates/global/CLAUDE.md");
519
- templateContent = await import_promises.default.readFile(fallbackTemplatePath, "utf-8");
520
- if (providerName === "gemini") {
521
- templateContent = templateContent.replace(/Claude/g, "Gemini");
563
+ if (providerName === "claude") {
564
+ try {
565
+ templateContent = await composeGlobalTemplate("standard");
566
+ } catch {
567
+ const fallbackTemplatePath = import_node_path3.default.join(getPackageRoot(), "templates/global/CLAUDE.md");
568
+ templateContent = await import_promises.default.readFile(fallbackTemplatePath, "utf-8");
569
+ }
570
+ } else {
571
+ const fallbackTemplatePath = import_node_path3.default.join(getPackageRoot(), "templates/global/CLAUDE.md");
572
+ templateContent = await import_promises.default.readFile(fallbackTemplatePath, "utf-8");
573
+ if (providerName === "gemini") {
574
+ templateContent = templateContent.replace(/Claude/g, "Gemini");
575
+ }
522
576
  }
523
577
  }
524
578
  let existingContent = "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prjct-cli",
3
- "version": "0.54.2",
3
+ "version": "0.55.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": {
@@ -91,9 +91,8 @@ ABORT.
91
91
  git diff --stat
92
92
  ```
93
93
 
94
+ Show the user:
94
95
  ```
95
- OUTPUT:
96
- """
97
96
  ## Commit Plan
98
97
 
99
98
  Branch: {currentBranch}
@@ -101,13 +100,38 @@ Changes:
101
100
  {git diff --stat output}
102
101
 
103
102
  Will create commit with prjct footer.
104
- Proceed? (yes/no)
105
- """
103
+ ```
104
+
105
+ Then ask for confirmation:
106
+
107
+ ```
108
+ AskUserQuestion:
109
+ question: "Create this commit?"
110
+ header: "Commit"
111
+ options:
112
+ - label: "Yes, commit (Recommended)"
113
+ description: "Stage all changes and commit"
114
+ - label: "No, cancel"
115
+ description: "Abort commit"
116
+ - label: "Show full diff"
117
+ description: "See detailed changes"
118
+ ```
119
+
120
+ **Handle responses:**
106
121
 
107
- WAIT for explicit approval.
108
- DO NOT assume.
122
+ **If "Show full diff":**
123
+ - Run `git diff` to show full changes
124
+ - Ask again with Yes/No options only
125
+
126
+ **If "No, cancel":**
127
+ ```
128
+ OUTPUT: "✅ Commit cancelled"
129
+ STOP - Do not continue
109
130
  ```
110
131
 
132
+ **If "Yes, commit":**
133
+ CONTINUE to Step 3
134
+
111
135
  ### Step 3: Stage and Commit
112
136
 
113
137
  ```bash
@@ -159,20 +183,38 @@ ABORT.
159
183
  git log origin/{currentBranch}..HEAD --oneline 2>/dev/null || git log --oneline -3
160
184
  ```
161
185
 
186
+ Show the user:
162
187
  ```
163
- OUTPUT:
164
- """
165
188
  ## Push Plan
166
189
 
167
190
  Branch: {currentBranch}
168
191
  Commits to push:
169
192
  {commits}
193
+ ```
194
+
195
+ Then ask for confirmation:
196
+
197
+ ```
198
+ AskUserQuestion:
199
+ question: "Push these commits?"
200
+ header: "Push"
201
+ options:
202
+ - label: "Yes, push (Recommended)"
203
+ description: "Push to remote origin"
204
+ - label: "No, cancel"
205
+ description: "Keep commits local"
206
+ ```
170
207
 
171
- Proceed? (yes/no)
172
- """
208
+ **Handle responses:**
173
209
 
174
- WAIT for explicit approval.
210
+ **If "No, cancel":**
175
211
  ```
212
+ OUTPUT: "✅ Push cancelled"
213
+ STOP - Do not continue
214
+ ```
215
+
216
+ **If "Yes, push":**
217
+ CONTINUE to Step 3
176
218
 
177
219
  ### Step 3: Execute Push
178
220
 
@@ -79,11 +79,31 @@ Will do:
79
79
  1. Merge PR with squash
80
80
  2. Delete feature branch
81
81
  3. Update local main
82
+ ```
83
+
84
+ Then ask for confirmation:
82
85
 
83
- Proceed? (yes/no)
86
+ ```
87
+ AskUserQuestion:
88
+ question: "Merge this PR?"
89
+ header: "Merge"
90
+ options:
91
+ - label: "Yes, merge (Recommended)"
92
+ description: "Squash merge and delete branch"
93
+ - label: "No, cancel"
94
+ description: "Keep PR open"
95
+ ```
96
+
97
+ **Handle responses:**
98
+
99
+ **If "No, cancel":**
100
+ ```
101
+ OUTPUT: "✅ Merge cancelled"
102
+ STOP - Do not continue
84
103
  ```
85
104
 
86
- **⛔ WAIT for explicit approval. Do not assume.**
105
+ **If "Yes, merge":**
106
+ CONTINUE to Step 4
87
107
 
88
108
  ---
89
109
 
@@ -69,11 +69,37 @@ Will do:
69
69
  4. Commit with prjct footer
70
70
  5. Push branch
71
71
  6. Create PR to main
72
+ ```
73
+
74
+ Then ask for confirmation:
75
+
76
+ ```
77
+ AskUserQuestion:
78
+ question: "Ready to ship these changes?"
79
+ header: "Ship"
80
+ options:
81
+ - label: "Yes, ship it (Recommended)"
82
+ description: "Run tests, bump version, create PR"
83
+ - label: "No, cancel"
84
+ description: "Abort ship operation"
85
+ - label: "Show full diff"
86
+ description: "See all file changes before deciding"
87
+ ```
88
+
89
+ **Handle responses:**
72
90
 
73
- Proceed? (yes/no)
91
+ **If "Show full diff":**
92
+ - Run `git diff` to show full changes
93
+ - Ask again with Yes/No options only
94
+
95
+ **If "No, cancel":**
96
+ ```
97
+ OUTPUT: "✅ Ship cancelled"
98
+ STOP - Do not continue
74
99
  ```
75
100
 
76
- **⛔ WAIT for explicit "yes" or approval. Do not assume.**
101
+ **If "Yes, ship it":**
102
+ CONTINUE to Step 3
77
103
 
78
104
  ---
79
105
 
@@ -123,9 +123,8 @@ ELSE:
123
123
 
124
124
  **⛔ DO NOT create branches or modify state without user approval.**
125
125
 
126
+ Show the user:
126
127
  ```
127
- OUTPUT:
128
- """
129
128
  ## Task Plan
130
129
 
131
130
  Description: $ARGUMENTS
@@ -137,13 +136,38 @@ Will do:
137
136
  2. Initialize task tracking in state.json
138
137
  3. Break down into subtasks
139
138
  4. {If Linear: Update issue status to In Progress}
139
+ ```
140
+
141
+ Then ask for confirmation:
142
+
143
+ ```
144
+ AskUserQuestion:
145
+ question: "Start this task?"
146
+ header: "Task"
147
+ options:
148
+ - label: "Yes, start task (Recommended)"
149
+ description: "Create branch and begin tracking"
150
+ - label: "No, cancel"
151
+ description: "Don't create task"
152
+ - label: "Modify plan"
153
+ description: "Change type, branch name, or subtasks"
154
+ ```
155
+
156
+ **Handle responses:**
140
157
 
141
- Proceed? (yes/no)
142
- """
158
+ **If "Modify plan":**
159
+ - Ask: "What would you like to change?"
160
+ - Update plan accordingly
161
+ - Ask again with Yes/No options only
143
162
 
144
- WAIT for explicit "yes" or approval
145
- DO NOT assume
163
+ **If "No, cancel":**
146
164
  ```
165
+ OUTPUT: "✅ Task creation cancelled"
166
+ STOP - Do not continue
167
+ ```
168
+
169
+ **If "Yes, start task":**
170
+ CONTINUE to Step B
147
171
 
148
172
  ### Step B: Explore Codebase
149
173
 
@@ -0,0 +1,70 @@
1
+ ## FAST vs SMART COMMANDS (CRITICAL)
2
+
3
+ **Some commands just run a CLI. Others need intelligence. Know the difference.**
4
+
5
+ ### FAST COMMANDS (Execute Immediately - NO planning, NO exploration)
6
+
7
+ | Command | Action | Time |
8
+ |---------|--------|------|
9
+ | `p. sync` | Run `prjct sync` | <5s |
10
+ | `p. next` | Run `prjct next` | <2s |
11
+ | `p. dash` | Run `prjct dash` | <2s |
12
+ | `p. pause` | Run `prjct pause` | <2s |
13
+ | `p. resume` | Run `prjct resume` | <2s |
14
+
15
+ **For these commands:**
16
+ ```
17
+ 1. Read template
18
+ 2. Run the CLI command shown
19
+ 3. Done
20
+ ```
21
+
22
+ **DO NOT:** explore codebase, create plans, ask questions, read project files
23
+
24
+ ### SMART COMMANDS (Require intelligence)
25
+
26
+ | Command | Why it needs intelligence |
27
+ |---------|--------------------------|
28
+ | `p. task` | Must explore codebase, break down work |
29
+ | `p. ship` | Must validate changes, create PR |
30
+ | `p. bug` | Must classify severity, find affected files |
31
+ | `p. done` | Must verify completion, update state |
32
+
33
+ **For these commands:** Follow the full INTELLIGENT BEHAVIOR rules.
34
+
35
+ ### Decision Rule
36
+ ```
37
+ IF template just says "run CLI command":
38
+ → Execute immediately, no planning
39
+ ELSE:
40
+ → Use intelligent behavior (explore, ask, plan)
41
+ ```
42
+
43
+ ---
44
+
45
+ ## CORE WORKFLOW
46
+
47
+ ```
48
+ p. sync → p. task "description" → [work] → p. done → p. ship
49
+ │ │ │ │
50
+ │ └─ Creates branch, breaks down │ │
51
+ │ task, starts tracking │ │
52
+ │ │ │
53
+ └─ Analyzes project, generates agents │ │
54
+ │ │
55
+ Completes subtask ─────┘ │
56
+
57
+ Ships feature, PR, tag ───┘
58
+ ```
59
+
60
+ ### Quick Reference
61
+
62
+ | Trigger | What It Does |
63
+ |---------|--------------|
64
+ | `p. sync` | Analyze project, generate domain agents |
65
+ | `p. task <desc>` | Start task with auto-classification |
66
+ | `p. done` | Complete current subtask |
67
+ | `p. ship [name]` | Ship feature with PR + version bump |
68
+ | `p. pause` | Pause current task |
69
+ | `p. resume` | Resume paused task |
70
+ | `p. bug <desc>` | Report bug with auto-priority |
@@ -0,0 +1,86 @@
1
+ # prjct-cli
2
+
3
+ **Context layer for AI agents** - Project context for Claude Code, Gemini CLI, and more.
4
+
5
+ ## HOW TO USE PRJCT
6
+
7
+ When user types `p. <command>`, load the template from `templates/commands/{command}.md` and execute it.
8
+
9
+ ```
10
+ p. sync → templates/commands/sync.md
11
+ p. task X → templates/commands/task.md
12
+ p. done → templates/commands/done.md
13
+ p. ship X → templates/commands/ship.md
14
+ ```
15
+
16
+ ---
17
+
18
+ ## CRITICAL RULES
19
+
20
+ ### 0. FOLLOW TEMPLATES STEP BY STEP (NON-NEGOTIABLE)
21
+
22
+ **Templates are MANDATORY WORKFLOWS, not suggestions.**
23
+
24
+ ```
25
+ 1. READ the template file COMPLETELY
26
+ 2. FOLLOW each step IN ORDER
27
+ 3. DO NOT skip steps - even "obvious" ones
28
+ 4. STOP at any BLOCKING condition
29
+ ```
30
+
31
+ ### 1. Path Resolution (MOST IMPORTANT)
32
+
33
+ **ALL writes go to global storage**: `~/.prjct-cli/projects/{projectId}/`
34
+
35
+ - **NEVER** write to `.prjct/` (config only, read-only)
36
+ - **NEVER** write to `./` (current directory)
37
+ - **ALWAYS** resolve projectId first from `.prjct/prjct.config.json`
38
+
39
+ ### 2. Before Any Command
40
+
41
+ ```
42
+ 1. Read .prjct/prjct.config.json → get projectId
43
+ 2. Set globalPath = ~/.prjct-cli/projects/{projectId}
44
+ 3. Execute command using globalPath for all writes
45
+ 4. Log to {globalPath}/memory/events.jsonl
46
+ ```
47
+
48
+ ### 3. Timestamps & UUIDs
49
+
50
+ ```bash
51
+ # Timestamp (NEVER hardcode)
52
+ bun -e "console.log(new Date().toISOString())" 2>/dev/null || node -e "console.log(new Date().toISOString())"
53
+
54
+ # UUID
55
+ bun -e "console.log(crypto.randomUUID())" 2>/dev/null || node -e "console.log(require('crypto').randomUUID())"
56
+ ```
57
+
58
+ ---
59
+
60
+ ## OUTPUT FORMAT
61
+
62
+ Concise responses (< 4 lines):
63
+ ```
64
+ [What was done]
65
+
66
+ [Key metrics]
67
+ Next: [suggested action]
68
+ ```
69
+
70
+ ---
71
+
72
+ ## CLEAN TERMINAL UX
73
+
74
+ **Tool calls MUST be user-friendly.**
75
+
76
+ 1. **ALWAYS use clear descriptions** in Bash tool calls:
77
+ - GOOD: `description: "Building project"`
78
+ - BAD: `description: "bun run build 2>&1 | tail -5"`
79
+
80
+ 2. **Hide implementation details** - Users don't need to see pipe chains, internal paths, JSON parsing
81
+
82
+ 3. **Use action verbs**: "Building project", "Running tests", "Checking git status"
83
+
84
+ ---
85
+
86
+ **Auto-managed by prjct-cli** | https://prjct.app
@@ -0,0 +1,50 @@
1
+ ## GIT WORKFLOW RULES (CRITICAL)
2
+
3
+ **NEVER commit directly to main/master**
4
+ - Always create a feature branch first
5
+ - Always create a PR for review
6
+ - Direct pushes to main are FORBIDDEN
7
+
8
+ **NEVER push without a PR**
9
+ - All changes go through pull requests
10
+ - No exceptions for "small fixes"
11
+
12
+ **NEVER skip version bump on ship**
13
+ - Every ship requires version update
14
+ - Every ship requires CHANGELOG entry
15
+
16
+ ### Git Commit Footer (CRITICAL - ALWAYS INCLUDE)
17
+
18
+ **Every commit made with prjct MUST include this footer:**
19
+
20
+ ```
21
+ Generated with [p/](https://www.prjct.app/)
22
+ ```
23
+
24
+ **This is NON-NEGOTIABLE. The prjct signature must appear in ALL commits.**
25
+
26
+ ### PLAN BEFORE DESTRUCTIVE ACTIONS
27
+
28
+ For commands that modify git state (ship, merge, done):
29
+ ```
30
+ 1. Show the user what will happen
31
+ 2. List all changes/files affected
32
+ 3. WAIT for explicit approval ("yes", "proceed", "do it")
33
+ 4. Only then execute
34
+ ```
35
+
36
+ **DO NOT assume approval. WAIT for it.**
37
+
38
+ ### BLOCKING CONDITIONS
39
+
40
+ When a template says "STOP" or has a blocking symbol:
41
+ ```
42
+ 1. HALT execution immediately
43
+ 2. TELL the user why you stopped
44
+ 3. DO NOT proceed until the condition is resolved
45
+ ```
46
+
47
+ **Examples of blockers:**
48
+ - `p. ship` on main branch → STOP, tell user to create branch
49
+ - `gh auth status` fails → STOP, tell user to authenticate
50
+ - No changes to commit → STOP, tell user nothing to ship
@@ -0,0 +1,92 @@
1
+ ## INTELLIGENT BEHAVIOR (For SMART commands only)
2
+
3
+ ### When Starting Tasks (`p. task`)
4
+ 1. **Analyze** - Understand what user wants to achieve
5
+ 2. **Classify** - Determine type: feature, bug, improvement, refactor, chore
6
+ 3. **Explore** - Find similar code, patterns, affected files
7
+ 4. **Ask** - Clarify ambiguities (use AskUserQuestion)
8
+ 5. **Design** - Propose 2-3 approaches, get approval
9
+ 6. **Break down** - Create actionable subtasks
10
+ 7. **Track** - Update storage/state.json
11
+
12
+ ### When Completing Tasks (`p. done`)
13
+ 1. Check if there are more subtasks
14
+ 2. If yes, advance to next subtask
15
+ 3. If no, task is complete
16
+ 4. Update storage, generate context
17
+
18
+ ### When Shipping (`p. ship`)
19
+ 1. Run tests (if configured)
20
+ 2. Create PR (if on feature branch)
21
+ 3. Bump version
22
+ 4. Update CHANGELOG
23
+ 5. Create git tag
24
+
25
+ ### Key Intelligence Rules
26
+ - **Read before write** - Always read existing files before modifying
27
+ - **Explore before coding** - Use Task(Explore) to understand codebase
28
+ - **Ask when uncertain** - Use AskUserQuestion to clarify
29
+ - **Log everything** - Append to memory/events.jsonl
30
+
31
+ ---
32
+
33
+ ## ARCHITECTURE: Write-Through Pattern
34
+
35
+ ```
36
+ User Action → Storage (JSON) → Context (MD) → Sync Events
37
+ ```
38
+
39
+ | Layer | Path | Purpose |
40
+ |-------|------|---------|
41
+ | **Storage** | `storage/*.json` | Source of truth |
42
+ | **Context** | `context/*.md` | Claude-readable summaries |
43
+ | **Memory** | `memory/events.jsonl` | Audit trail (append-only) |
44
+ | **Agents** | `agents/*.md` | Domain specialists |
45
+ | **Sync** | `sync/pending.json` | Backend sync queue |
46
+
47
+ ### File Structure
48
+ ```
49
+ ~/.prjct-cli/projects/{projectId}/
50
+ ├── storage/
51
+ │ ├── state.json # Current task (SOURCE OF TRUTH)
52
+ │ ├── queue.json # Task queue
53
+ │ └── shipped.json # Shipped features
54
+ ├── context/
55
+ │ ├── now.md # Current task (generated)
56
+ │ └── next.md # Queue (generated)
57
+ ├── memory/
58
+ │ └── events.jsonl # Audit trail
59
+ ├── agents/ # Domain specialists (auto-generated)
60
+ └── sync/
61
+ └── pending.json # Events for backend
62
+ ```
63
+
64
+ ---
65
+
66
+ ## LOADING DOMAIN AGENTS
67
+
68
+ When working on tasks, load relevant agents from `{globalPath}/agents/`:
69
+ - `frontend.md` - Frontend patterns, components
70
+ - `backend.md` - Backend patterns, APIs
71
+ - `database.md` - Database patterns, queries
72
+ - `uxui.md` - UX/UI guidelines
73
+ - `testing.md` - Testing patterns
74
+ - `devops.md` - CI/CD, containers
75
+
76
+ These agents contain project-specific patterns. **USE THEM**.
77
+
78
+ ---
79
+
80
+ ## SKILL INTEGRATION
81
+
82
+ Agents are linked to Claude Code skills from claude-plugins.dev.
83
+
84
+ ### How Skills Work
85
+
86
+ 1. **During `p. sync`**: Search claude-plugins.dev, install best matches
87
+ 2. **During `p. task`**: Skills are auto-invoked for domain expertise
88
+ 3. **Agent frontmatter** has `skills: [discovered-skill-name]` field
89
+
90
+ ### Skill Location
91
+
92
+ Skills are markdown files in `~/.claude/skills/`
@@ -0,0 +1,50 @@
1
+ ## STORAGE RULES (CROSS-AGENT COMPATIBILITY)
2
+
3
+ **NEVER use temporary files** - Write directly to final destination:
4
+ - WRONG: Create `.tmp/file.json`, then `mv` to final path
5
+ - CORRECT: Write directly to `{globalPath}/storage/state.json`
6
+
7
+ **JSON formatting** - Always use consistent format:
8
+ - 2-space indentation
9
+ - No trailing commas
10
+ - Keys in logical order (as defined in storage schemas)
11
+
12
+ **Atomic writes for JSON**:
13
+ ```javascript
14
+ // Read → Modify → Write (no temp files)
15
+ const data = JSON.parse(fs.readFileSync(path, 'utf-8'))
16
+ data.newField = value
17
+ fs.writeFileSync(path, JSON.stringify(data, null, 2))
18
+ ```
19
+
20
+ **Timestamps**: Always ISO-8601 with milliseconds (`.000Z`)
21
+ **UUIDs**: Always v4 format (lowercase)
22
+ **Line endings**: LF (not CRLF)
23
+ **Encoding**: UTF-8 without BOM
24
+
25
+ **NEVER**:
26
+ - Use `.tmp/` directories
27
+ - Use `mv` or `rename` operations for storage files
28
+ - Create backup files like `*.bak` or `*.old`
29
+ - Modify existing lines in `events.jsonl`
30
+
31
+ **Full specification**: See `{npm root -g}/prjct-cli/templates/global/STORAGE-SPEC.md`
32
+
33
+ ---
34
+
35
+ ## Preserve Markers (User Customizations)
36
+
37
+ User customizations in context files and agents survive regeneration using preserve markers:
38
+
39
+ ```markdown
40
+ <!-- prjct:preserve -->
41
+ # My Custom Rules
42
+ - Always use tabs
43
+ - Prefer functional patterns
44
+ <!-- /prjct:preserve -->
45
+ ```
46
+
47
+ **How it works:**
48
+ - Content between markers is extracted before regeneration
49
+ - After regeneration, preserved content is appended under "Your Customizations"
50
+ - Named sections: `<!-- prjct:preserve:my-rules -->` for identification
@@ -0,0 +1,36 @@
1
+ {
2
+ "description": "Configuration for modular CLAUDE.md composition",
3
+ "version": "1.0.0",
4
+ "profiles": {
5
+ "full": {
6
+ "description": "All modules - maximum context (~2300 tokens)",
7
+ "modules": [
8
+ "CLAUDE-core.md",
9
+ "CLAUDE-commands.md",
10
+ "CLAUDE-git.md",
11
+ "CLAUDE-storage.md",
12
+ "CLAUDE-intelligence.md"
13
+ ]
14
+ },
15
+ "standard": {
16
+ "description": "Standard modules - balanced (~1400 tokens)",
17
+ "modules": ["CLAUDE-core.md", "CLAUDE-commands.md", "CLAUDE-git.md"]
18
+ },
19
+ "minimal": {
20
+ "description": "Core only - minimum context (~500 tokens)",
21
+ "modules": ["CLAUDE-core.md"]
22
+ }
23
+ },
24
+ "default": "standard",
25
+ "commandProfiles": {
26
+ "sync": "minimal",
27
+ "next": "minimal",
28
+ "dash": "minimal",
29
+ "pause": "minimal",
30
+ "resume": "minimal",
31
+ "task": "full",
32
+ "done": "standard",
33
+ "ship": "full",
34
+ "bug": "full"
35
+ }
36
+ }