@gadmin2n/cli 0.0.103 → 0.0.105

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.
@@ -388,8 +388,8 @@ function copyTreeOverwrite(srcDir, destDir) {
388
388
  * Resolve which git URL + branch to use for the .claude context sync.
389
389
  *
390
390
  * Priority (highest first):
391
- * 1. CLI --claude-context-repo (full URL override)
392
- * 2. CLI --claude-context-protocol (https | ssh | skip)
391
+ * 1. CLI --coding-context-repo (full URL override)
392
+ * 2. CLI --coding-context-protocol (https | ssh | skip)
393
393
  * 3. Env var GADMIN2_CLAUDE_CONTEXT_REPO (full URL)
394
394
  * 4. Env var GADMIN2_CLAUDE_CONTEXT_PROTOCOL (https | ssh | skip)
395
395
  * 5. --yes (auto mode): default to built-in HTTPS, no prompt
@@ -638,6 +638,18 @@ class UpdateAction extends abstract_action_1.AbstractAction {
638
638
  console.error(chalk.red(`Unknown subcommand "${subcommand}". Expected: (none) | diff`));
639
639
  process.exit(1);
640
640
  }
641
+ // Guard: refuse to run anywhere except a gadmin2 instance project root.
642
+ // The canonical marker is server/gadmin-cli.json (created by `gadmin2 n`).
643
+ // We check this up-front so commands like `gadmin2 update --no-template`
644
+ // can't silently sync .claude into an unrelated directory.
645
+ const instanceMarker = path.join(process.cwd(), 'server', 'gadmin-cli.json');
646
+ if (!fs.existsSync(instanceMarker)) {
647
+ console.error(chalk.red('\nNot at a gadmin2 instance project root.\n' +
648
+ ` Expected to find: server/gadmin-cli.json\n` +
649
+ ` Current directory: ${process.cwd()}\n\n` +
650
+ 'Run this command from the project root created by `gadmin2 n <name>`.\n'));
651
+ process.exit(1);
652
+ }
641
653
  // Pre-flight version check (B): silent network call to npm registry,
642
654
  // hard 1.5s timeout, never blocks. Prints a yellow banner if outdated.
643
655
  const versionCheckEnabled = ((_b = options.find((o) => o.name === 'version-check')) === null || _b === void 0 ? void 0 : _b.value) !==
@@ -747,313 +759,329 @@ class UpdateAction extends abstract_action_1.AbstractAction {
747
759
  });
748
760
  }
749
761
  handleUpdate(options) {
750
- var _a, _b, _c, _d, _e, _f, _g;
762
+ var _a, _b, _c, _d, _e, _f, _g, _h;
751
763
  return __awaiter(this, void 0, void 0, function* () {
752
764
  const dryRun = (_a = options.find((o) => o.name === 'dry-run')) === null || _a === void 0 ? void 0 : _a.value;
753
765
  const autoYes = (_b = options.find((o) => o.name === 'yes')) === null || _b === void 0 ? void 0 : _b.value;
754
766
  const smartMergeEnabled = (_d = (_c = options.find((o) => o.name === 'smart-merge')) === null || _c === void 0 ? void 0 : _c.value) !== null && _d !== void 0 ? _d : true;
755
- let templateDir;
756
- let excludes;
757
- try {
758
- const config = loadTemplateConfig();
759
- templateDir = resolveTemplatePath(config.templateName);
760
- excludes = config.excludes;
761
- }
762
- catch (err) {
763
- console.error(chalk.red(err.message));
767
+ const templateEnabled = ((_e = options.find((o) => o.name === 'template')) === null || _e === void 0 ? void 0 : _e.value) !== false;
768
+ const claudeEnabled = ((_f = options.find((o) => o.name === 'coding-context')) === null || _f === void 0 ? void 0 : _f.value) !==
769
+ false;
770
+ if (!templateEnabled && !claudeEnabled) {
771
+ console.error(chalk.red('\nNothing to do: both --no-template and --no-coding-context were specified.\n'));
764
772
  process.exit(1);
765
773
  }
766
774
  const instanceDir = process.cwd();
767
- console.info(`\n${chalk.gray('Template:')} ${templateDir}`);
768
- console.info(`${chalk.gray('Instance:')} ${instanceDir}\n`);
769
- // Build smart-merger registry (or null if disabled)
770
- const userTemplateUpdate = smartMergeEnabled
771
- ? (0, template_merge_1.loadTemplateUpdateConfig)(instanceDir)
772
- : null;
773
- const registry = smartMergeEnabled
774
- ? buildMergerRegistry(userTemplateUpdate)
775
- : null;
776
- const userPolicies = userTemplateUpdate === null || userTemplateUpdate === void 0 ? void 0 : userTemplateUpdate.policies;
777
- // Snapshot package.json files BEFORE any writes. We watch:
778
- // - <instanceDir>/package.json (root, if present — workspace / husky / etc.)
779
- // - <instanceDir>/web/package.json
780
- // - <instanceDir>/server/package.json
781
- const pkgSnapshots = [
782
- snapshotPackageJson(instanceDir, '(root)'),
783
- snapshotPackageJson(path.join(instanceDir, 'web'), 'web'),
784
- snapshotPackageJson(path.join(instanceDir, 'server'), 'server'),
785
- ];
786
- const templateFiles = collectFiles(templateDir, excludes);
787
- const instanceFiles = collectFiles(instanceDir, excludes);
788
- const instanceSet = new Set(instanceFiles);
789
- const toUpdate = [];
790
- const toAdd = [];
791
- for (const file of templateFiles) {
792
- if (instanceSet.has(file)) {
793
- const tContent = fs.readFileSync(path.join(templateDir, file), 'utf-8');
794
- const iContent = fs.readFileSync(path.join(instanceDir, file), 'utf-8');
795
- if (tContent !== iContent) {
796
- toUpdate.push(file);
797
- }
775
+ // Hoisted so stage 3 (auto-install) can see snapshots taken in stage 1.
776
+ // Remains null when --no-template, which is also the signal to skip stage 3.
777
+ let pkgSnapshots = null;
778
+ if (templateEnabled) {
779
+ let templateDir;
780
+ let excludes;
781
+ try {
782
+ const config = loadTemplateConfig();
783
+ templateDir = resolveTemplatePath(config.templateName);
784
+ excludes = config.excludes;
798
785
  }
799
- else {
800
- toAdd.push(file);
786
+ catch (err) {
787
+ console.error(chalk.red(err.message));
788
+ process.exit(1);
801
789
  }
802
- }
803
- if (toUpdate.length === 0 && toAdd.length === 0) {
804
- console.info(chalk.green('✅ Instance is up to date with template.\n'));
805
- }
806
- else {
807
- console.info(chalk.bold('═══════════════════════════════════════════════════'));
808
- console.info(chalk.bold(' Template Update Summary'));
809
- console.info(chalk.bold('═══════════════════════════════════════════════════\n'));
810
- if (toUpdate.length > 0) {
811
- console.info(chalk.yellow(`📝 To update (${toUpdate.length} files)`));
812
- for (const f of toUpdate) {
813
- console.info(` ${f}`);
790
+ console.info(`\n${chalk.gray('Template:')} ${templateDir}`);
791
+ console.info(`${chalk.gray('Instance:')} ${instanceDir}\n`);
792
+ // Build smart-merger registry (or null if disabled)
793
+ const userTemplateUpdate = smartMergeEnabled
794
+ ? (0, template_merge_1.loadTemplateUpdateConfig)(instanceDir)
795
+ : null;
796
+ const registry = smartMergeEnabled
797
+ ? buildMergerRegistry(userTemplateUpdate)
798
+ : null;
799
+ const userPolicies = userTemplateUpdate === null || userTemplateUpdate === void 0 ? void 0 : userTemplateUpdate.policies;
800
+ // Snapshot package.json files BEFORE any writes. We watch:
801
+ // - <instanceDir>/package.json (root, if present — workspace / husky / etc.)
802
+ // - <instanceDir>/web/package.json
803
+ // - <instanceDir>/server/package.json
804
+ pkgSnapshots = [
805
+ snapshotPackageJson(instanceDir, '(root)'),
806
+ snapshotPackageJson(path.join(instanceDir, 'web'), 'web'),
807
+ snapshotPackageJson(path.join(instanceDir, 'server'), 'server'),
808
+ ];
809
+ const templateFiles = collectFiles(templateDir, excludes);
810
+ const instanceFiles = collectFiles(instanceDir, excludes);
811
+ const instanceSet = new Set(instanceFiles);
812
+ const toUpdate = [];
813
+ const toAdd = [];
814
+ for (const file of templateFiles) {
815
+ if (instanceSet.has(file)) {
816
+ const tContent = fs.readFileSync(path.join(templateDir, file), 'utf-8');
817
+ const iContent = fs.readFileSync(path.join(instanceDir, file), 'utf-8');
818
+ if (tContent !== iContent) {
819
+ toUpdate.push(file);
820
+ }
814
821
  }
815
- console.info();
816
- }
817
- if (toAdd.length > 0) {
818
- console.info(chalk.green(`➕ To add (${toAdd.length} files)`));
819
- for (const f of toAdd) {
820
- console.info(` ${f}`);
822
+ else {
823
+ toAdd.push(file);
821
824
  }
822
- console.info();
823
825
  }
824
- console.info(chalk.gray('───────────────────────────────────────────────────'));
825
- console.info(chalk.gray(` Total: ${toUpdate.length} update | ${toAdd.length} add`));
826
- console.info(chalk.gray('───────────────────────────────────────────────────\n'));
827
- if (!dryRun) {
828
- if (!autoYes) {
829
- const { confirm } = yield inquirer.prompt([
830
- {
831
- type: 'confirm',
832
- name: 'confirm',
833
- message: 'Proceed with template update?',
834
- default: false,
835
- },
836
- ]);
837
- if (!confirm) {
838
- console.info('Cancelled.\n');
839
- return;
826
+ if (toUpdate.length === 0 && toAdd.length === 0) {
827
+ console.info(chalk.green('✅ Instance is up to date with template.\n'));
828
+ }
829
+ else {
830
+ console.info(chalk.bold('═══════════════════════════════════════════════════'));
831
+ console.info(chalk.bold(' Template Update Summary'));
832
+ console.info(chalk.bold('═══════════════════════════════════════════════════\n'));
833
+ if (toUpdate.length > 0) {
834
+ console.info(chalk.yellow(`📝 To update (${toUpdate.length} files)`));
835
+ for (const f of toUpdate) {
836
+ console.info(` ${f}`);
840
837
  }
838
+ console.info();
841
839
  }
842
- let skippedCount = 0;
843
- let overwrittenCount = 0;
844
- let mergedCount = 0;
845
- let conflictCount = 0;
846
- let smartCount = 0;
847
- let applyAll = null;
848
- for (let i = 0; i < toUpdate.length; i++) {
849
- const file = toUpdate[i];
850
- const tContent = fs.readFileSync(path.join(templateDir, file), 'utf-8');
851
- const iContent = fs.readFileSync(path.join(instanceDir, file), 'utf-8');
852
- // 1. Try smart merger if registered for this file
853
- const smartMerger = registry
854
- ? registry.resolve(file)
855
- : null;
856
- let smartResult = null;
857
- if (smartMerger) {
858
- try {
859
- smartResult = yield smartMerger.merge({
860
- filePath: file,
861
- instancePath: path.join(instanceDir, file),
862
- templatePath: path.join(templateDir, file),
863
- instanceContent: iContent,
864
- templateContent: tContent,
865
- policies: userPolicies,
866
- });
867
- }
868
- catch (err) {
869
- smartResult = {
870
- ok: false,
871
- reason: (err === null || err === void 0 ? void 0 : err.message) || String(err),
872
- };
873
- }
840
+ if (toAdd.length > 0) {
841
+ console.info(chalk.green(`➕ To add (${toAdd.length} files)`));
842
+ for (const f of toAdd) {
843
+ console.info(` ${f}`);
874
844
  }
875
- let strategy = applyAll;
876
- if (!strategy) {
877
- if (autoYes) {
878
- // autoYes: prefer smart merge when available, else conflict markers
879
- strategy =
880
- smartResult && smartResult.ok ? 'smart' : 'conflict';
845
+ console.info();
846
+ }
847
+ console.info(chalk.gray('───────────────────────────────────────────────────'));
848
+ console.info(chalk.gray(` Total: ${toUpdate.length} update | ${toAdd.length} add`));
849
+ console.info(chalk.gray('───────────────────────────────────────────────────\n'));
850
+ if (!dryRun) {
851
+ if (!autoYes) {
852
+ const { confirm } = yield inquirer.prompt([
853
+ {
854
+ type: 'confirm',
855
+ name: 'confirm',
856
+ message: 'Proceed with template update?',
857
+ default: false,
858
+ },
859
+ ]);
860
+ if (!confirm) {
861
+ console.info('Cancelled.\n');
862
+ return;
881
863
  }
882
- else {
883
- const diff = createUnifiedDiff(file, iContent, tContent);
884
- console.info(`\n${chalk.bold(`[${i + 1}/${toUpdate.length}]`)} ${chalk.yellow(file)}`);
885
- console.info(diff);
886
- console.info();
887
- const choices = [];
888
- if (smartResult && smartResult.ok) {
889
- const summary = smartResult.notes.slice(0, 3).join('; ');
890
- const more = smartResult.notes.length > 3 ? '...' : '';
891
- choices.push({
892
- name: `Smart merge — ${smartMerger.name} (recommended): ${summary}${more}`,
893
- value: 'smart',
894
- short: 'smart',
895
- });
896
- }
897
- else if (smartMerger) {
898
- const reason = smartResult && !smartResult.ok
899
- ? smartResult.reason
900
- : 'no result';
901
- choices.push({
902
- name: `Smart merge — ${smartMerger.name} (UNAVAILABLE: ${reason})`,
903
- value: 'smart-unavailable',
904
- short: 'smart-unavailable',
905
- disabled: reason,
864
+ }
865
+ let skippedCount = 0;
866
+ let overwrittenCount = 0;
867
+ let mergedCount = 0;
868
+ let conflictCount = 0;
869
+ let smartCount = 0;
870
+ let applyAll = null;
871
+ for (let i = 0; i < toUpdate.length; i++) {
872
+ const file = toUpdate[i];
873
+ const tContent = fs.readFileSync(path.join(templateDir, file), 'utf-8');
874
+ const iContent = fs.readFileSync(path.join(instanceDir, file), 'utf-8');
875
+ // 1. Try smart merger if registered for this file
876
+ const smartMerger = registry
877
+ ? registry.resolve(file)
878
+ : null;
879
+ let smartResult = null;
880
+ if (smartMerger) {
881
+ try {
882
+ smartResult = yield smartMerger.merge({
883
+ filePath: file,
884
+ instancePath: path.join(instanceDir, file),
885
+ templatePath: path.join(templateDir, file),
886
+ instanceContent: iContent,
887
+ templateContent: tContent,
888
+ policies: userPolicies,
906
889
  });
907
890
  }
908
- choices.push({
909
- name: 'Skip — keep instance as-is',
910
- value: 'skip',
911
- short: 'skip',
912
- }, {
913
- name: 'Overwrite — use template version',
914
- value: 'overwrite',
915
- short: 'overwrite',
916
- }, {
917
- name: 'Auto merge — attempt git merge',
918
- value: 'merge',
919
- short: 'merge',
920
- }, {
921
- name: 'Conflict markers — write both versions for manual resolve',
922
- value: 'conflict',
923
- short: 'conflict',
924
- }, new inquirer.Separator());
925
- if (smartResult && smartResult.ok) {
926
- choices.push({
927
- name: 'Smart-merge ALL remaining (where supported)',
928
- value: 'smart-all',
929
- short: 'smart all',
930
- });
891
+ catch (err) {
892
+ smartResult = {
893
+ ok: false,
894
+ reason: (err === null || err === void 0 ? void 0 : err.message) || String(err),
895
+ };
931
896
  }
932
- choices.push({
933
- name: 'Skip ALL remaining',
934
- value: 'skip-all',
935
- short: 'skip all',
936
- }, {
937
- name: 'Overwrite ALL remaining',
938
- value: 'overwrite-all',
939
- short: 'overwrite all',
940
- }, {
941
- name: 'Conflict markers ALL remaining',
942
- value: 'conflict-all',
943
- short: 'conflict all',
944
- });
945
- const { action } = yield inquirer.prompt([
946
- {
947
- type: 'list',
948
- name: 'action',
949
- message: `How to handle ${file}?`,
950
- default: smartResult && smartResult.ok ? 'smart' : 'skip',
951
- choices,
952
- },
953
- ]);
954
- if (action.endsWith('-all')) {
955
- applyAll = action.replace('-all', '');
956
- strategy = applyAll;
897
+ }
898
+ let strategy = applyAll;
899
+ if (!strategy) {
900
+ if (autoYes) {
901
+ // autoYes: prefer smart merge when available, else conflict markers
902
+ strategy =
903
+ smartResult && smartResult.ok ? 'smart' : 'conflict';
957
904
  }
958
905
  else {
959
- strategy = action;
906
+ const diff = createUnifiedDiff(file, iContent, tContent);
907
+ console.info(`\n${chalk.bold(`[${i + 1}/${toUpdate.length}]`)} ${chalk.yellow(file)}`);
908
+ console.info(diff);
909
+ console.info();
910
+ const choices = [];
911
+ if (smartResult && smartResult.ok) {
912
+ const summary = smartResult.notes.slice(0, 3).join('; ');
913
+ const more = smartResult.notes.length > 3 ? '...' : '';
914
+ choices.push({
915
+ name: `Smart merge — ${smartMerger.name} (recommended): ${summary}${more}`,
916
+ value: 'smart',
917
+ short: 'smart',
918
+ });
919
+ }
920
+ else if (smartMerger) {
921
+ const reason = smartResult && !smartResult.ok
922
+ ? smartResult.reason
923
+ : 'no result';
924
+ choices.push({
925
+ name: `Smart merge — ${smartMerger.name} (UNAVAILABLE: ${reason})`,
926
+ value: 'smart-unavailable',
927
+ short: 'smart-unavailable',
928
+ disabled: reason,
929
+ });
930
+ }
931
+ choices.push({
932
+ name: 'Skip — keep instance as-is',
933
+ value: 'skip',
934
+ short: 'skip',
935
+ }, {
936
+ name: 'Overwrite — use template version',
937
+ value: 'overwrite',
938
+ short: 'overwrite',
939
+ }, {
940
+ name: 'Auto merge — attempt git merge',
941
+ value: 'merge',
942
+ short: 'merge',
943
+ }, {
944
+ name: 'Conflict markers — write both versions for manual resolve',
945
+ value: 'conflict',
946
+ short: 'conflict',
947
+ }, new inquirer.Separator());
948
+ if (smartResult && smartResult.ok) {
949
+ choices.push({
950
+ name: 'Smart-merge ALL remaining (where supported)',
951
+ value: 'smart-all',
952
+ short: 'smart all',
953
+ });
954
+ }
955
+ choices.push({
956
+ name: 'Skip ALL remaining',
957
+ value: 'skip-all',
958
+ short: 'skip all',
959
+ }, {
960
+ name: 'Overwrite ALL remaining',
961
+ value: 'overwrite-all',
962
+ short: 'overwrite all',
963
+ }, {
964
+ name: 'Conflict markers ALL remaining',
965
+ value: 'conflict-all',
966
+ short: 'conflict all',
967
+ });
968
+ const { action } = yield inquirer.prompt([
969
+ {
970
+ type: 'list',
971
+ name: 'action',
972
+ message: `How to handle ${file}?`,
973
+ default: smartResult && smartResult.ok ? 'smart' : 'skip',
974
+ choices,
975
+ },
976
+ ]);
977
+ if (action.endsWith('-all')) {
978
+ applyAll = action.replace('-all', '');
979
+ strategy = applyAll;
980
+ }
981
+ else {
982
+ strategy = action;
983
+ }
960
984
  }
961
985
  }
962
- }
963
- const destPath = path.join(instanceDir, file);
964
- switch (strategy) {
965
- case 'smart':
966
- if (smartResult && smartResult.ok) {
967
- fs.writeFileSync(destPath, smartResult.merged, 'utf-8');
968
- smartCount++;
969
- console.info(chalk.green(` ✓ ${file} smart merge (${smartMerger.name})`));
970
- for (const note of smartResult.notes) {
971
- console.info(chalk.gray(` • ${note}`));
986
+ const destPath = path.join(instanceDir, file);
987
+ switch (strategy) {
988
+ case 'smart':
989
+ if (smartResult && smartResult.ok) {
990
+ fs.writeFileSync(destPath, smartResult.merged, 'utf-8');
991
+ smartCount++;
992
+ console.info(chalk.green(` ✓ ${file} — smart merge (${smartMerger.name})`));
993
+ for (const note of smartResult.notes) {
994
+ console.info(chalk.gray(` • ${note}`));
995
+ }
996
+ }
997
+ else {
998
+ // No smart merger usable: fallback to conflict markers
999
+ fs.writeFileSync(destPath, createConflictContent(iContent, tContent), 'utf-8');
1000
+ conflictCount++;
1001
+ console.info(chalk.yellow(` ⚠️ ${file} — no smart merger available, wrote conflict markers`));
1002
+ }
1003
+ break;
1004
+ case 'skip':
1005
+ skippedCount++;
1006
+ break;
1007
+ case 'overwrite':
1008
+ fs.writeFileSync(destPath, tContent, 'utf-8');
1009
+ overwrittenCount++;
1010
+ break;
1011
+ case 'merge': {
1012
+ const result = tryAutoMerge(iContent, tContent);
1013
+ fs.writeFileSync(destPath, result.merged, 'utf-8');
1014
+ if (result.hasConflicts) {
1015
+ conflictCount++;
1016
+ console.info(chalk.yellow(` ⚠️ ${file} — merge has conflicts`));
972
1017
  }
1018
+ else {
1019
+ mergedCount++;
1020
+ console.info(chalk.green(` ✓ ${file} — merged cleanly`));
1021
+ }
1022
+ break;
973
1023
  }
974
- else {
975
- // No smart merger usable: fallback to conflict markers
1024
+ case 'conflict':
1025
+ default:
976
1026
  fs.writeFileSync(destPath, createConflictContent(iContent, tContent), 'utf-8');
977
1027
  conflictCount++;
978
- console.info(chalk.yellow(` ⚠️ ${file} — no smart merger available, wrote conflict markers`));
979
- }
980
- break;
981
- case 'skip':
982
- skippedCount++;
983
- break;
984
- case 'overwrite':
985
- fs.writeFileSync(destPath, tContent, 'utf-8');
986
- overwrittenCount++;
987
- break;
988
- case 'merge': {
989
- const result = tryAutoMerge(iContent, tContent);
990
- fs.writeFileSync(destPath, result.merged, 'utf-8');
991
- if (result.hasConflicts) {
992
- conflictCount++;
993
- console.info(chalk.yellow(` ⚠️ ${file} — merge has conflicts`));
994
- }
995
- else {
996
- mergedCount++;
997
- console.info(chalk.green(` ✓ ${file} — merged cleanly`));
998
- }
999
- break;
1028
+ break;
1000
1029
  }
1001
- case 'conflict':
1002
- default:
1003
- fs.writeFileSync(destPath, createConflictContent(iContent, tContent), 'utf-8');
1004
- conflictCount++;
1005
- break;
1030
+ }
1031
+ for (const file of toAdd) {
1032
+ const destPath = path.join(instanceDir, file);
1033
+ fs.mkdirSync(path.dirname(destPath), { recursive: true });
1034
+ fs.copyFileSync(path.join(templateDir, file), destPath);
1035
+ }
1036
+ console.info(chalk.bold('\n✅ Template update complete!\n'));
1037
+ const stats = [];
1038
+ if (overwrittenCount > 0)
1039
+ stats.push(`${overwrittenCount} overwritten`);
1040
+ if (smartCount > 0)
1041
+ stats.push(`${smartCount} smart-merged`);
1042
+ if (mergedCount > 0)
1043
+ stats.push(`${mergedCount} merged`);
1044
+ if (conflictCount > 0)
1045
+ stats.push(`${conflictCount} with conflicts`);
1046
+ if (skippedCount > 0)
1047
+ stats.push(`${skippedCount} skipped`);
1048
+ if (toAdd.length > 0)
1049
+ stats.push(`${toAdd.length} added`);
1050
+ console.info(` ${stats.join(' | ')}\n`);
1051
+ if (conflictCount > 0) {
1052
+ console.info(chalk.yellow(`⚠️ ${conflictCount} files have conflict markers. ` +
1053
+ `Search for "<<<<<<< INSTANCE" to resolve them.\n`));
1006
1054
  }
1007
1055
  }
1008
- for (const file of toAdd) {
1009
- const destPath = path.join(instanceDir, file);
1010
- fs.mkdirSync(path.dirname(destPath), { recursive: true });
1011
- fs.copyFileSync(path.join(templateDir, file), destPath);
1012
- }
1013
- console.info(chalk.bold('\n✅ Template update complete!\n'));
1014
- const stats = [];
1015
- if (overwrittenCount > 0)
1016
- stats.push(`${overwrittenCount} overwritten`);
1017
- if (smartCount > 0)
1018
- stats.push(`${smartCount} smart-merged`);
1019
- if (mergedCount > 0)
1020
- stats.push(`${mergedCount} merged`);
1021
- if (conflictCount > 0)
1022
- stats.push(`${conflictCount} with conflicts`);
1023
- if (skippedCount > 0)
1024
- stats.push(`${skippedCount} skipped`);
1025
- if (toAdd.length > 0)
1026
- stats.push(`${toAdd.length} added`);
1027
- console.info(` ${stats.join(' | ')}\n`);
1028
- if (conflictCount > 0) {
1029
- console.info(chalk.yellow(`⚠️ ${conflictCount} files have conflict markers. ` +
1030
- `Search for "<<<<<<< INSTANCE" to resolve them.\n`));
1056
+ else {
1057
+ console.info(chalk.gray('(dry-run mode, no template changes made)\n'));
1031
1058
  }
1032
1059
  }
1033
- else {
1034
- console.info(chalk.gray('(dry-run mode, no template changes made)\n'));
1035
- }
1060
+ }
1061
+ else {
1062
+ console.info(chalk.gray('\nSkipping template update (--no-template).\n'));
1036
1063
  }
1037
1064
  // 2. Sync .claude context (regardless of whether template had changes)
1038
- const claudeEnabled = ((_e = options.find((o) => o.name === 'claude-context')) === null || _e === void 0 ? void 0 : _e.value) !==
1039
- false;
1040
1065
  if (claudeEnabled) {
1041
- const cliRepo = ((_f = options.find((o) => o.name === 'claude-context-repo')) === null || _f === void 0 ? void 0 : _f.value) ||
1066
+ const cliRepo = ((_g = options.find((o) => o.name === 'coding-context-repo')) === null || _g === void 0 ? void 0 : _g.value) ||
1042
1067
  '';
1043
- const cliProtocol = ((_g = options.find((o) => o.name === 'claude-context-protocol')) === null || _g === void 0 ? void 0 : _g.value) || '';
1068
+ const cliProtocol = ((_h = options.find((o) => o.name === 'coding-context-protocol')) === null || _h === void 0 ? void 0 : _h.value) || '';
1044
1069
  const claudeSource = yield resolveClaudeContextSource(cliRepo, cliProtocol, autoYes);
1045
1070
  if (claudeSource) {
1046
1071
  yield syncClaudeContext(instanceDir, dryRun, claudeSource);
1047
1072
  }
1048
1073
  else {
1049
- console.info(chalk.gray('\nSkipping .claude context sync (per user choice / --no-claude-context).\n'));
1074
+ console.info(chalk.gray('\nSkipping .claude context sync (per user choice / --no-coding-context).\n'));
1050
1075
  }
1051
1076
  }
1052
1077
  else {
1053
- console.info(chalk.gray('\nSkipping .claude context sync (--no-claude-context).\n'));
1078
+ console.info(chalk.gray('\nSkipping .claude context sync (--no-coding-context).\n'));
1079
+ }
1080
+ // 3. If web/server package.json changed during template update, install deps.
1081
+ // Skipped entirely under --no-template (no snapshots taken → nothing to compare).
1082
+ if (pkgSnapshots) {
1083
+ yield maybeInstallChangedDeps(pkgSnapshots, dryRun);
1054
1084
  }
1055
- // 3. If web/server package.json changed during template update, install deps
1056
- yield maybeInstallChangedDeps(pkgSnapshots, dryRun);
1057
1085
  });
1058
1086
  }
1059
1087
  }
@@ -21,6 +21,9 @@ class UpdateCommand extends abstract_command_1.AbstractCommand {
21
21
  ' (none) Pull updates from template into instance, sync .claude → .claude-internal/.agent,\n' +
22
22
  ' and run install in web/ or server/ if their package.json changed.\n' +
23
23
  ' diff Show differences between instance and template (read-only).\n\n' +
24
+ ' Stage toggles (combine to run only what you want):\n' +
25
+ ' --no-template Skip stage 1 (template merge) and stage 3 (auto-install).\n' +
26
+ ' --no-coding-context Skip stage 2 (.claude context sync).\n\n' +
24
27
  ' Update strategies (per-file interactive prompt):\n' +
25
28
  ' skip Keep instance file as-is\n' +
26
29
  ' overwrite Replace with template version\n' +
@@ -31,9 +34,10 @@ class UpdateCommand extends abstract_command_1.AbstractCommand {
31
34
  .option('-y, --yes', '[update] Skip per-file prompt, use conflict markers for all.')
32
35
  .option('--no-smart-merge', '[update] Disable smart structural mergers; use line-level prompt for every file.')
33
36
  .option('--no-version-check', '[update | diff] Skip the npm registry version check at startup.')
34
- .option('--claude-context-repo <url>', '[update] Fully override the .claude context repo URL (highest priority).')
35
- .option('--claude-context-protocol <proto>', '[update] Force protocol for the built-in context repo. One of: https | ssh | skip.')
36
- .option('--no-claude-context', '[update] Skip the .claude context sync stage entirely.')
37
+ .option('--coding-context-repo <url>', '[update] Fully override the .claude context repo URL (highest priority).')
38
+ .option('--coding-context-protocol <proto>', '[update] Force protocol for the built-in context repo. One of: https | ssh | skip.')
39
+ .option('--no-coding-context', '[update] Skip the .claude context sync stage entirely.')
40
+ .option('--no-template', '[update] Skip the template merge stage and the auto-install stage; only run .claude context sync.')
37
41
  .action((subcommand, command) => __awaiter(this, void 0, void 0, function* () {
38
42
  const inputs = [];
39
43
  inputs.push({ name: 'subcommand', value: subcommand || '' });
@@ -44,17 +48,21 @@ class UpdateCommand extends abstract_command_1.AbstractCommand {
44
48
  options.push({ name: 'smart-merge', value: command.smartMerge !== false });
45
49
  options.push({ name: 'version-check', value: command.versionCheck !== false });
46
50
  options.push({
47
- name: 'claude-context-repo',
51
+ name: 'coding-context-repo',
48
52
  value: command.claudeContextRepo || '',
49
53
  });
50
54
  options.push({
51
- name: 'claude-context-protocol',
55
+ name: 'coding-context-protocol',
52
56
  value: command.claudeContextProtocol || '',
53
57
  });
54
58
  options.push({
55
- name: 'claude-context',
59
+ name: 'coding-context',
56
60
  value: command.claudeContext !== false,
57
61
  });
62
+ options.push({
63
+ name: 'template',
64
+ value: command.template !== false,
65
+ });
58
66
  yield this.action.handle(inputs, options);
59
67
  }));
60
68
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gadmin2n/cli",
3
- "version": "0.0.103",
3
+ "version": "0.0.105",
4
4
  "description": "Gadmin - modern, fast, powerful node.js web framework (@cli)",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -47,7 +47,7 @@
47
47
  "@angular-devkit/core": "13.3.2",
48
48
  "@angular-devkit/schematics": "13.3.2",
49
49
  "@angular-devkit/schematics-cli": "13.3.2",
50
- "@gadmin2n/schematics": "^0.0.85",
50
+ "@gadmin2n/schematics": "^0.0.86",
51
51
  "abc": "^0.6.1",
52
52
  "chalk": "3.0.0",
53
53
  "chokidar": "3.5.3",