@mmnto/cli 1.0.0 → 1.2.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/dist/adapters/gh-utils.test.js +2 -0
- package/dist/adapters/gh-utils.test.js.map +1 -1
- package/dist/adapters/github-cli-pr.test.js +2 -0
- package/dist/adapters/github-cli-pr.test.js.map +1 -1
- package/dist/adapters/github-cli.test.js +2 -0
- package/dist/adapters/github-cli.test.js.map +1 -1
- package/dist/assets/compiled-baseline.d.ts.map +1 -1
- package/dist/assets/compiled-baseline.js +47 -8
- package/dist/assets/compiled-baseline.js.map +1 -1
- package/dist/commands/compile.d.ts.map +1 -1
- package/dist/commands/compile.js +110 -0
- package/dist/commands/compile.js.map +1 -1
- package/dist/commands/docs.d.ts +1 -1
- package/dist/commands/docs.d.ts.map +1 -1
- package/dist/commands/docs.js +22 -15
- package/dist/commands/docs.js.map +1 -1
- package/dist/commands/docs.test.js +4 -3
- package/dist/commands/docs.test.js.map +1 -1
- package/dist/commands/explain.d.ts +2 -0
- package/dist/commands/explain.d.ts.map +1 -0
- package/dist/commands/explain.js +99 -0
- package/dist/commands/explain.js.map +1 -0
- package/dist/commands/init.d.ts +3 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +235 -217
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/lint.d.ts.map +1 -1
- package/dist/commands/lint.js +29 -3
- package/dist/commands/lint.js.map +1 -1
- package/dist/commands/run-compiled-rules.d.ts.map +1 -1
- package/dist/commands/run-compiled-rules.js +12 -3
- package/dist/commands/run-compiled-rules.js.map +1 -1
- package/dist/commands/spec.d.ts +1 -1
- package/dist/commands/spec.d.ts.map +1 -1
- package/dist/commands/spec.js +36 -7
- package/dist/commands/spec.js.map +1 -1
- package/dist/commands/test-rules.d.ts.map +1 -1
- package/dist/commands/test-rules.js +1 -1
- package/dist/commands/test-rules.js.map +1 -1
- package/dist/git.test.js +2 -0
- package/dist/git.test.js.map +1 -1
- package/dist/index.js +15 -2
- package/dist/index.js.map +1 -1
- package/dist/orchestrators/conformance.test.js +1 -0
- package/dist/orchestrators/conformance.test.js.map +1 -1
- package/dist/orchestrators/shell-orchestrator.test.js +1 -0
- package/dist/orchestrators/shell-orchestrator.test.js.map +1 -1
- package/package.json +2 -2
package/dist/commands/init.js
CHANGED
|
@@ -688,7 +688,7 @@ function applyReflexUpgrade(filePath) {
|
|
|
688
688
|
fs.writeFileSync(filePath, updated, 'utf-8');
|
|
689
689
|
return clean;
|
|
690
690
|
}
|
|
691
|
-
export async function initCommand() {
|
|
691
|
+
export async function initCommand(options) {
|
|
692
692
|
const cwd = process.cwd();
|
|
693
693
|
const configPath = path.join(cwd, 'totem.config.ts');
|
|
694
694
|
const totemDir = path.join(cwd, '.totem');
|
|
@@ -700,68 +700,81 @@ export async function initCommand() {
|
|
|
700
700
|
if (!configExists) {
|
|
701
701
|
// --- Fresh install: generate config ---
|
|
702
702
|
log.info('Totem', 'Scanning project...');
|
|
703
|
-
|
|
704
|
-
const detections = [];
|
|
705
|
-
if (detected.hasTypeScript)
|
|
706
|
-
detections.push('TypeScript');
|
|
707
|
-
if (detected.hasSrc)
|
|
708
|
-
detections.push('src/');
|
|
709
|
-
if (detected.hasDocs)
|
|
710
|
-
detections.push('docs/');
|
|
711
|
-
if (detected.hasSpecs)
|
|
712
|
-
detections.push('specs/');
|
|
713
|
-
if (detected.hasContext)
|
|
714
|
-
detections.push('context/');
|
|
715
|
-
if (detected.hasSessions)
|
|
716
|
-
detections.push('session logs');
|
|
717
|
-
if (detections.length > 0) {
|
|
718
|
-
log.info('Totem', `Detected: ${bold(detections.join(', '))}`);
|
|
719
|
-
}
|
|
720
|
-
else {
|
|
721
|
-
log.dim('Totem', 'No specific project structure detected. Using markdown defaults.');
|
|
722
|
-
}
|
|
723
|
-
const targets = buildTargets(detected);
|
|
724
|
-
// Auto-detect embedding tier from environment
|
|
703
|
+
let targets = [];
|
|
725
704
|
let embeddingTier = detectEmbeddingTier(cwd);
|
|
726
|
-
if (
|
|
727
|
-
log.info('Totem', `
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
705
|
+
if (options?.bare) {
|
|
706
|
+
log.info('Totem', `Initializing in ${bold('bare mode')} (non-code repository)`);
|
|
707
|
+
targets = [
|
|
708
|
+
{ glob: '.totem/lessons/*.md', type: 'lesson', strategy: 'markdown-heading' },
|
|
709
|
+
{ glob: '.totem/lessons.md', type: 'lesson', strategy: 'markdown-heading' },
|
|
710
|
+
{ glob: '**/*.md', type: 'spec', strategy: 'markdown-heading' },
|
|
711
|
+
];
|
|
712
|
+
embeddingTier = 'none'; // Force Lite tier for bare repos
|
|
731
713
|
}
|
|
732
714
|
else {
|
|
733
|
-
|
|
734
|
-
const
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
715
|
+
const detected = detectProject(cwd);
|
|
716
|
+
const detections = [];
|
|
717
|
+
if (detected.hasTypeScript)
|
|
718
|
+
detections.push('TypeScript');
|
|
719
|
+
if (detected.hasSrc)
|
|
720
|
+
detections.push('src/');
|
|
721
|
+
if (detected.hasDocs)
|
|
722
|
+
detections.push('docs/');
|
|
723
|
+
if (detected.hasSpecs)
|
|
724
|
+
detections.push('specs/');
|
|
725
|
+
if (detected.hasContext)
|
|
726
|
+
detections.push('context/');
|
|
727
|
+
if (detected.hasSessions)
|
|
728
|
+
detections.push('session logs');
|
|
729
|
+
if (detections.length > 0) {
|
|
730
|
+
log.info('Totem', `Detected: ${bold(detections.join(', '))}`);
|
|
731
|
+
}
|
|
732
|
+
else {
|
|
733
|
+
log.dim('Totem', 'No specific project structure detected. Using markdown defaults.');
|
|
739
734
|
}
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
735
|
+
targets = buildTargets(detected);
|
|
736
|
+
if (embeddingTier === 'openai') {
|
|
737
|
+
log.info('Totem', `Detected ${bold('OPENAI_API_KEY')} in environment. Using OpenAI embeddings.`);
|
|
738
|
+
}
|
|
739
|
+
else if (embeddingTier === 'gemini') {
|
|
740
|
+
log.info('Totem', `Detected ${bold('GEMINI_API_KEY')} in environment. Using Gemini embeddings (single-key DX).`);
|
|
741
|
+
}
|
|
742
|
+
else {
|
|
743
|
+
// No key detected — prompt the user
|
|
744
|
+
const answer = await rl.question('Enter your OpenAI API key, type "ollama" for a local model, or press Enter for Lite tier: ');
|
|
745
|
+
const input = answer.trim().replace(/[\r\n]/g, '');
|
|
746
|
+
if (input.toLowerCase() === 'ollama') {
|
|
747
|
+
embeddingTier = 'ollama';
|
|
748
|
+
log.info('Totem', 'Configured for Ollama. Make sure it is running locally.');
|
|
743
749
|
}
|
|
744
|
-
else {
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
if (fs.existsSync(envPath)) {
|
|
748
|
-
const existing = fs.readFileSync(envPath, 'utf-8');
|
|
749
|
-
if (!/^\s*OPENAI_API_KEY\s*=/m.test(existing)) {
|
|
750
|
-
const prefix = existing.length > 0 && !existing.endsWith('\n') ? '\n' : '';
|
|
751
|
-
fs.appendFileSync(envPath, prefix + envLine);
|
|
752
|
-
}
|
|
750
|
+
else if (input) {
|
|
751
|
+
if (!/^sk-[a-zA-Z0-9_-]+$/.test(input)) {
|
|
752
|
+
log.warn('Totem', 'API key does not look like a valid OpenAI key (expected sk-...). Starting in Lite tier.');
|
|
753
753
|
}
|
|
754
754
|
else {
|
|
755
|
-
|
|
755
|
+
const envPath = path.join(cwd, '.env');
|
|
756
|
+
const envLine = `OPENAI_API_KEY="${input}"\n`;
|
|
757
|
+
if (fs.existsSync(envPath)) {
|
|
758
|
+
const existing = fs.readFileSync(envPath, 'utf-8');
|
|
759
|
+
if (!/^\s*OPENAI_API_KEY\s*=/m.test(existing)) {
|
|
760
|
+
const prefix = existing.length > 0 && !existing.endsWith('\n') ? '\n' : '';
|
|
761
|
+
fs.appendFileSync(envPath, prefix + envLine);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
else {
|
|
765
|
+
fs.writeFileSync(envPath, envLine);
|
|
766
|
+
}
|
|
767
|
+
embeddingTier = 'openai';
|
|
768
|
+
summary.push({ file: '.env', action: 'Saved OpenAI API key' });
|
|
756
769
|
}
|
|
757
|
-
embeddingTier = 'openai';
|
|
758
|
-
summary.push({ file: '.env', action: 'Saved OpenAI API key' });
|
|
759
770
|
}
|
|
760
771
|
}
|
|
761
772
|
}
|
|
762
773
|
if (embeddingTier === 'none') {
|
|
763
774
|
log.info('Totem', `Starting in ${bold('Lite')} tier (add-lesson, bridge, eject only).`);
|
|
764
|
-
|
|
775
|
+
if (!options?.bare) {
|
|
776
|
+
log.dim('Totem', 'Set OPENAI_API_KEY and re-run `totem init` to unlock sync/search/shield.');
|
|
777
|
+
}
|
|
765
778
|
}
|
|
766
779
|
const configContent = await generateConfig(targets, embeddingTier, cwd);
|
|
767
780
|
fs.writeFileSync(configPath, configContent, 'utf-8');
|
|
@@ -817,194 +830,199 @@ export async function initCommand() {
|
|
|
817
830
|
log.dim('Totem', `Could not install pre-compiled rules: ${err instanceof Error ? err.message : String(err)}`);
|
|
818
831
|
}
|
|
819
832
|
}
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
const
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
const pick = await rl.question(` Configure ${tool.name}? (Y/n): `);
|
|
835
|
-
if (pick.trim().toLowerCase() !== 'n' && pick.trim().toLowerCase() !== 'no') {
|
|
836
|
-
selectedTools.push(tool);
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
else {
|
|
841
|
-
// 'all' or Enter (default)
|
|
842
|
-
selectedTools = detectedTools;
|
|
843
|
-
}
|
|
844
|
-
// --- MCP scaffolding for selected tools ---
|
|
845
|
-
for (const tool of selectedTools) {
|
|
846
|
-
if (!tool.mcpPath || !tool.serverEntry)
|
|
847
|
-
continue;
|
|
848
|
-
const filePath = path.join(cwd, tool.mcpPath);
|
|
849
|
-
const result = scaffoldMcpConfig(filePath, tool.serverEntry);
|
|
850
|
-
if (result.err) {
|
|
851
|
-
log.error('Totem Error', result.err); // totem-ignore — result.err is internal scaffolding error, not LLM output
|
|
852
|
-
console.error(`To fix this, add the following manually to your ${tool.mcpPath} under "mcpServers":\n`);
|
|
853
|
-
console.error(` "totem": ${JSON.stringify(tool.serverEntry, null, 2)}\n`);
|
|
833
|
+
if (options?.bare) {
|
|
834
|
+
log.info('Totem', 'Skipping AI tool and hook installation for bare mode.');
|
|
835
|
+
}
|
|
836
|
+
else {
|
|
837
|
+
// --- Unified AI tool selection ---
|
|
838
|
+
const detectedTools = detectAiTools(cwd);
|
|
839
|
+
if (detectedTools.length > 0) {
|
|
840
|
+
const toolNames = detectedTools.map((t) => t.name).join(', ');
|
|
841
|
+
log.info('Totem', `Detected AI tools: ${bold(toolNames)}`);
|
|
842
|
+
const toolAnswer = await rl.question('Which tools should Totem configure? [all/none/select] (default: all): ');
|
|
843
|
+
let selectedTools;
|
|
844
|
+
const trimmed = toolAnswer.trim().toLowerCase();
|
|
845
|
+
if (trimmed === 'none') {
|
|
846
|
+
selectedTools = [];
|
|
854
847
|
}
|
|
855
|
-
else if (
|
|
856
|
-
|
|
848
|
+
else if (trimmed === 'select') {
|
|
849
|
+
selectedTools = [];
|
|
850
|
+
for (const tool of detectedTools) {
|
|
851
|
+
const pick = await rl.question(` Configure ${tool.name}? (Y/n): `);
|
|
852
|
+
if (pick.trim().toLowerCase() !== 'n' && pick.trim().toLowerCase() !== 'no') {
|
|
853
|
+
selectedTools.push(tool);
|
|
854
|
+
}
|
|
855
|
+
}
|
|
857
856
|
}
|
|
858
|
-
else
|
|
859
|
-
|
|
857
|
+
else {
|
|
858
|
+
// 'all' or Enter (default)
|
|
859
|
+
selectedTools = detectedTools;
|
|
860
860
|
}
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
summary.push({ file: tool.reflexFile, action: 'Injected memory reflexes (v2)' });
|
|
861
|
+
// --- MCP scaffolding for selected tools ---
|
|
862
|
+
for (const tool of selectedTools) {
|
|
863
|
+
if (!tool.mcpPath || !tool.serverEntry)
|
|
864
|
+
continue;
|
|
865
|
+
const filePath = path.join(cwd, tool.mcpPath);
|
|
866
|
+
const result = scaffoldMcpConfig(filePath, tool.serverEntry);
|
|
867
|
+
if (result.err) {
|
|
868
|
+
log.error('Totem Error', result.err); // totem-ignore — result.err is internal scaffolding error, not LLM output
|
|
869
|
+
console.error(`To fix this, add the following manually to your ${tool.mcpPath} under "mcpServers":\n`);
|
|
870
|
+
console.error(` "totem": ${JSON.stringify(tool.serverEntry, null, 2)}\n`);
|
|
872
871
|
}
|
|
873
|
-
else if (result === '
|
|
874
|
-
|
|
872
|
+
else if (result.action === 'created') {
|
|
873
|
+
summary.push({ file: tool.mcpPath, action: `Created with Totem MCP server` });
|
|
874
|
+
}
|
|
875
|
+
else if (result.action === 'merged') {
|
|
876
|
+
summary.push({ file: tool.mcpPath, action: `Added totem to mcpServers` });
|
|
875
877
|
}
|
|
876
878
|
}
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
879
|
+
// --- Reflex injection & upgrade for selected tools ---
|
|
880
|
+
const outdatedFiles = [];
|
|
881
|
+
for (const tool of selectedTools) {
|
|
882
|
+
if (!tool.reflexFile)
|
|
883
|
+
continue;
|
|
884
|
+
const filePath = path.join(cwd, tool.reflexFile);
|
|
885
|
+
try {
|
|
886
|
+
const result = injectReflexes(filePath);
|
|
887
|
+
if (result === 'injected') {
|
|
888
|
+
summary.push({ file: tool.reflexFile, action: 'Injected memory reflexes (v2)' });
|
|
889
|
+
}
|
|
890
|
+
else if (result === 'outdated') {
|
|
891
|
+
outdatedFiles.push({ tool, filePath });
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
catch (err) {
|
|
895
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
896
|
+
log.error('Totem Error', `Failed to inject reflexes into ${tool.reflexFile}: ${message}`);
|
|
897
|
+
}
|
|
896
898
|
}
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
899
|
+
// Prompt once for all outdated reflex files
|
|
900
|
+
if (outdatedFiles.length > 0) {
|
|
901
|
+
const fileList = outdatedFiles.map((f) => f.tool.reflexFile).join(', ');
|
|
902
|
+
log.warn('Totem', `Outdated reflexes found in: ${bold(fileList)}`);
|
|
903
|
+
let shouldUpgrade = false;
|
|
904
|
+
if (process.stdin.isTTY) {
|
|
905
|
+
const answer = await rl.question(`Upgrade reflexes to v${REFLEX_VERSION}? (Y/n): `);
|
|
906
|
+
shouldUpgrade =
|
|
907
|
+
answer.trim().toLowerCase() !== 'n' && answer.trim().toLowerCase() !== 'no';
|
|
908
|
+
}
|
|
909
|
+
else {
|
|
910
|
+
// Non-TTY (CI/scripted): auto-upgrade to match baseline lessons behavior
|
|
911
|
+
shouldUpgrade = true;
|
|
912
|
+
log.info('Totem', 'Non-interactive mode — auto-upgrading reflexes.');
|
|
913
|
+
}
|
|
914
|
+
if (shouldUpgrade) {
|
|
915
|
+
for (const { tool, filePath } of outdatedFiles) {
|
|
916
|
+
try {
|
|
917
|
+
const clean = applyReflexUpgrade(filePath);
|
|
918
|
+
if (clean) {
|
|
919
|
+
summary.push({
|
|
920
|
+
file: tool.reflexFile,
|
|
921
|
+
action: `Upgraded reflexes to v${REFLEX_VERSION}`,
|
|
922
|
+
});
|
|
923
|
+
}
|
|
924
|
+
else {
|
|
925
|
+
summary.push({
|
|
926
|
+
file: tool.reflexFile,
|
|
927
|
+
action: `Appended v${REFLEX_VERSION} reflexes (manual cleanup needed — remove old block)`,
|
|
928
|
+
});
|
|
929
|
+
log.warn('Totem', `Could not cleanly replace old reflexes in ${tool.reflexFile}. New block appended — please remove the old one manually.`);
|
|
930
|
+
}
|
|
906
931
|
}
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
action: `Appended v${REFLEX_VERSION} reflexes (manual cleanup needed — remove old block)`,
|
|
911
|
-
});
|
|
912
|
-
log.warn('Totem', `Could not cleanly replace old reflexes in ${tool.reflexFile}. New block appended — please remove the old one manually.`);
|
|
932
|
+
catch (err) {
|
|
933
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
934
|
+
log.error('Totem Error', `Failed to upgrade reflexes in ${tool.reflexFile}: ${message}`);
|
|
913
935
|
}
|
|
914
936
|
}
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
937
|
+
}
|
|
938
|
+
else {
|
|
939
|
+
for (const { tool } of outdatedFiles) {
|
|
940
|
+
summary.push({
|
|
941
|
+
file: tool.reflexFile,
|
|
942
|
+
action: 'Outdated reflexes — upgrade declined',
|
|
943
|
+
});
|
|
918
944
|
}
|
|
919
945
|
}
|
|
920
946
|
}
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
947
|
+
// --- Hook installation for selected tools ---
|
|
948
|
+
for (const tool of selectedTools) {
|
|
949
|
+
if (!tool.hookInstaller)
|
|
950
|
+
continue;
|
|
951
|
+
const results = await tool.hookInstaller(cwd);
|
|
952
|
+
for (const result of results) {
|
|
953
|
+
if (result.err) {
|
|
954
|
+
log.error('Totem Error', `Hook scaffolding failed for ${result.file}: ${result.err}`); // totem-ignore — internal hook installer error
|
|
955
|
+
}
|
|
956
|
+
else if (result.action === 'created') {
|
|
957
|
+
summary.push({ file: result.file, action: `Scaffolded ${tool.name} hook` });
|
|
958
|
+
}
|
|
959
|
+
else if (result.action === 'merged') {
|
|
960
|
+
summary.push({
|
|
961
|
+
file: result.file,
|
|
962
|
+
action: `Merged ${tool.name} hook into existing config`,
|
|
963
|
+
});
|
|
964
|
+
}
|
|
927
965
|
}
|
|
928
966
|
}
|
|
929
967
|
}
|
|
930
|
-
// ---
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
log.error('Totem Error', `Hook scaffolding failed for ${result.file}: ${result.err}`); // totem-ignore — internal hook installer error
|
|
938
|
-
}
|
|
939
|
-
else if (result.action === 'created') {
|
|
940
|
-
summary.push({ file: result.file, action: `Scaffolded ${tool.name} hook` });
|
|
941
|
-
}
|
|
942
|
-
else if (result.action === 'merged') {
|
|
943
|
-
summary.push({
|
|
944
|
-
file: result.file,
|
|
945
|
-
action: `Merged ${tool.name} hook into existing config`,
|
|
946
|
-
});
|
|
947
|
-
}
|
|
948
|
-
}
|
|
968
|
+
// --- Always run: enforcement hooks (pre-commit + pre-push) ---
|
|
969
|
+
const enforcement = await installEnforcementHooks(cwd, rl);
|
|
970
|
+
if (enforcement.preCommit === 'installed' || enforcement.preCommit === 'appended') {
|
|
971
|
+
summary.push({
|
|
972
|
+
file: '.git/hooks/pre-commit',
|
|
973
|
+
action: `${enforcement.preCommit === 'installed' ? 'Installed' : 'Appended'} main-branch protection`,
|
|
974
|
+
});
|
|
949
975
|
}
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
file: '.git/hooks/pre-commit',
|
|
956
|
-
action: `${enforcement.preCommit === 'installed' ? 'Installed' : 'Appended'} main-branch protection`,
|
|
957
|
-
});
|
|
958
|
-
}
|
|
959
|
-
else if (enforcement.preCommit === 'skipped-non-shell') {
|
|
960
|
-
summary.push({
|
|
961
|
-
file: '.git/hooks/pre-commit',
|
|
962
|
-
action: 'Skipped — non-shell hook detected (manual integration needed)',
|
|
963
|
-
});
|
|
964
|
-
}
|
|
965
|
-
if (enforcement.prePush === 'installed' || enforcement.prePush === 'appended') {
|
|
966
|
-
summary.push({
|
|
967
|
-
file: '.git/hooks/pre-push',
|
|
968
|
-
action: `${enforcement.prePush === 'installed' ? 'Installed' : 'Appended'} deterministic shield gate`,
|
|
969
|
-
});
|
|
970
|
-
}
|
|
971
|
-
else if (enforcement.prePush === 'skipped-non-shell') {
|
|
972
|
-
summary.push({
|
|
973
|
-
file: '.git/hooks/pre-push',
|
|
974
|
-
action: 'Skipped — non-shell hook detected (manual integration needed)',
|
|
975
|
-
});
|
|
976
|
-
}
|
|
977
|
-
// --- Always run: post-merge git hook ---
|
|
978
|
-
await installPostMergeHook(cwd, rl);
|
|
979
|
-
// --- Always run: .gitignore ---
|
|
980
|
-
const gitignorePath = path.join(cwd, '.gitignore');
|
|
981
|
-
if (fs.existsSync(gitignorePath)) {
|
|
982
|
-
const gitignore = fs.readFileSync(gitignorePath, 'utf-8');
|
|
983
|
-
if (!gitignore.includes('.lancedb')) {
|
|
984
|
-
fs.appendFileSync(gitignorePath, '\n# Totem\n.lancedb/\n');
|
|
985
|
-
summary.push({ file: '.gitignore', action: 'Added .lancedb/ exclusion' });
|
|
976
|
+
else if (enforcement.preCommit === 'skipped-non-shell') {
|
|
977
|
+
summary.push({
|
|
978
|
+
file: '.git/hooks/pre-commit',
|
|
979
|
+
action: 'Skipped — non-shell hook detected (manual integration needed)',
|
|
980
|
+
});
|
|
986
981
|
}
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
if (
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
982
|
+
if (enforcement.prePush === 'installed' || enforcement.prePush === 'appended') {
|
|
983
|
+
summary.push({
|
|
984
|
+
file: '.git/hooks/pre-push',
|
|
985
|
+
action: `${enforcement.prePush === 'installed' ? 'Installed' : 'Appended'} deterministic shield gate`,
|
|
986
|
+
});
|
|
987
|
+
}
|
|
988
|
+
else if (enforcement.prePush === 'skipped-non-shell') {
|
|
989
|
+
summary.push({
|
|
990
|
+
file: '.git/hooks/pre-push',
|
|
991
|
+
action: 'Skipped — non-shell hook detected (manual integration needed)',
|
|
992
|
+
});
|
|
993
|
+
}
|
|
994
|
+
// --- Always run: post-merge git hook ---
|
|
995
|
+
await installPostMergeHook(cwd, rl);
|
|
996
|
+
// --- Always run: .gitignore ---
|
|
997
|
+
const gitignorePath = path.join(cwd, '.gitignore');
|
|
998
|
+
if (fs.existsSync(gitignorePath)) {
|
|
999
|
+
const gitignore = fs.readFileSync(gitignorePath, 'utf-8');
|
|
1000
|
+
if (!gitignore.includes('.lancedb')) {
|
|
1001
|
+
fs.appendFileSync(gitignorePath, '\n# Totem\n.lancedb/\n');
|
|
1002
|
+
summary.push({ file: '.gitignore', action: 'Added .lancedb/ exclusion' });
|
|
1001
1003
|
}
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1004
|
+
}
|
|
1005
|
+
// --- Auto-ingest cursor rules (ADR-048) ---
|
|
1006
|
+
const { scanCursorInstructions } = await import('@mmnto/totem');
|
|
1007
|
+
const cursorInstructions = scanCursorInstructions(cwd);
|
|
1008
|
+
if (cursorInstructions.length > 0) {
|
|
1009
|
+
const answer = await rl.question(`\nFound ${cursorInstructions.length} existing AI rule(s) (.cursorrules / .mdc). Compile into deterministic invariants? (Y/n): `);
|
|
1010
|
+
if (answer.trim().toLowerCase() !== 'n' && answer.trim().toLowerCase() !== 'no') {
|
|
1011
|
+
try {
|
|
1012
|
+
const { compileCommand } = await import('./compile.js');
|
|
1013
|
+
await compileCommand({ fromCursor: true });
|
|
1014
|
+
summary.push({
|
|
1015
|
+
file: '.totem/compiled-rules.json',
|
|
1016
|
+
action: `Compiled ${cursorInstructions.length} cursor rule(s) into invariants`,
|
|
1017
|
+
});
|
|
1018
|
+
}
|
|
1019
|
+
catch (err) {
|
|
1020
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
1021
|
+
log.warn('Totem', `Could not compile cursor rules: ${detail}`);
|
|
1022
|
+
}
|
|
1005
1023
|
}
|
|
1006
1024
|
}
|
|
1007
|
-
}
|
|
1025
|
+
} // end of bare mode else block
|
|
1008
1026
|
// --- Print summary ---
|
|
1009
1027
|
if (summary.length > 0) {
|
|
1010
1028
|
console.error(`\n${brand('--- Totem Init Summary ---')}`);
|