contextforge-cli-ai-prompt-pirates 0.4.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.
Files changed (72) hide show
  1. package/.contextforge/config.ai.example.json +28 -0
  2. package/.contextforge/config.example.json +29 -0
  3. package/.contextforge/prompts/README.md +50 -0
  4. package/.env.example +73 -0
  5. package/LICENSE +21 -0
  6. package/README.md +223 -0
  7. package/bin/contextforge.js +14 -0
  8. package/bin/postinstall-worker.js +14 -0
  9. package/bin/postinstall.js +26 -0
  10. package/package.json +65 -0
  11. package/prompts/README.md +50 -0
  12. package/prompts/response-schema.md +22 -0
  13. package/prompts/retry-addon.md +1 -0
  14. package/prompts/system.md +10 -0
  15. package/prompts/user-template.md +22 -0
  16. package/src/analyzers/ast-parser.js +139 -0
  17. package/src/analyzers/bugs-lite.js +118 -0
  18. package/src/analyzers/changelog.js +190 -0
  19. package/src/analyzers/code-insights.js +225 -0
  20. package/src/analyzers/dependencies.js +110 -0
  21. package/src/analyzers/detection-quality.js +106 -0
  22. package/src/analyzers/eslint-runner.js +56 -0
  23. package/src/analyzers/folder-tree.js +60 -0
  24. package/src/analyzers/git-insights.js +94 -0
  25. package/src/analyzers/project-meta.js +98 -0
  26. package/src/cli/commands/changes.js +36 -0
  27. package/src/cli/commands/doctor.js +152 -0
  28. package/src/cli/commands/generate.js +7 -0
  29. package/src/cli/commands/init.js +98 -0
  30. package/src/cli/commands/prompt.js +53 -0
  31. package/src/cli/commands/stop.js +10 -0
  32. package/src/cli/commands/summary.js +22 -0
  33. package/src/cli/commands/watch.js +9 -0
  34. package/src/cli/index.js +120 -0
  35. package/src/core/confidence.js +51 -0
  36. package/src/core/config.js +183 -0
  37. package/src/core/cursor-rules.js +293 -0
  38. package/src/core/ensure-setup.js +45 -0
  39. package/src/core/env.js +113 -0
  40. package/src/core/package-meta.js +35 -0
  41. package/src/core/paths.js +39 -0
  42. package/src/core/pipeline.js +256 -0
  43. package/src/core/postinstall.js +168 -0
  44. package/src/core/setup-env.js +86 -0
  45. package/src/core/setup-prompts.js +31 -0
  46. package/src/core/snapshot-hash.js +70 -0
  47. package/src/detectors/architecture.js +117 -0
  48. package/src/detectors/auth-deploy.js +51 -0
  49. package/src/detectors/database.js +127 -0
  50. package/src/detectors/tech-stack.js +180 -0
  51. package/src/generators/artifacts.js +44 -0
  52. package/src/generators/changes-markdown.js +72 -0
  53. package/src/generators/context-json.js +91 -0
  54. package/src/generators/context-markdown.js +571 -0
  55. package/src/generators/mermaid.js +59 -0
  56. package/src/index.js +13 -0
  57. package/src/scanner/change-detector.js +24 -0
  58. package/src/scanner/config-files.js +52 -0
  59. package/src/scanner/file-index.js +55 -0
  60. package/src/scanner/package-deps.js +32 -0
  61. package/src/scanner/repo-scanner.js +143 -0
  62. package/src/services/ai/background.js +87 -0
  63. package/src/services/ai/config.js +48 -0
  64. package/src/services/ai/enrich.js +87 -0
  65. package/src/services/ai/index.js +3 -0
  66. package/src/services/ai/prompt-loader.js +104 -0
  67. package/src/services/ai/prompt.js +42 -0
  68. package/src/services/ai/providers/groq.js +36 -0
  69. package/src/services/ai/providers/openai.js +32 -0
  70. package/src/services/ai/redact.js +33 -0
  71. package/src/services/ai/validate.js +70 -0
  72. package/src/watcher/watcher.js +128 -0
@@ -0,0 +1,152 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'node:path';
3
+ import { loadConfig, isInitialized } from '../../core/config.js';
4
+ import {
5
+ loadProjectEnv,
6
+ hasAnyAiKey,
7
+ isAutoStartEnabledViaEnv,
8
+ isPostinstallWatchEnabledViaEnv,
9
+ } from '../../core/env.js';
10
+ import { getPidPath } from '../../core/paths.js';
11
+ import { validateEnvSetup } from '../../core/setup-env.js';
12
+ import { isAiAvailable } from '../../services/ai/index.js';
13
+ import { getContextForgeRoot, getOutputPath, OUTPUT_FILES } from '../../core/paths.js';
14
+
15
+ export async function doctorCommand(projectRoot) {
16
+ const root = path.resolve(projectRoot);
17
+ await loadProjectEnv(root);
18
+
19
+ const checks = [];
20
+ let ok = true;
21
+
22
+ const nodeVersion = process.version;
23
+ const nodeMajor = parseInt(process.versions.node.split('.')[0], 10);
24
+ if (nodeMajor >= 18) {
25
+ checks.push({ name: 'Node.js', status: 'ok', detail: nodeVersion });
26
+ } else {
27
+ checks.push({ name: 'Node.js', status: 'fail', detail: `${nodeVersion} — need >= 18` });
28
+ ok = false;
29
+ }
30
+
31
+ if (await isInitialized(root)) {
32
+ checks.push({ name: 'ContextForge init', status: 'ok', detail: getContextForgeRoot(root) });
33
+ } else {
34
+ checks.push({ name: 'ContextForge init', status: 'fail', detail: 'Run: npx contextforge init' });
35
+ ok = false;
36
+ }
37
+
38
+ const contextMd = getOutputPath(root, OUTPUT_FILES.contextMd);
39
+ if (await fs.pathExists(contextMd)) {
40
+ const stat = await fs.stat(contextMd);
41
+ checks.push({
42
+ name: 'context.md',
43
+ status: 'ok',
44
+ detail: `Exists (${Math.round(stat.size / 1024)} KB, ${stat.mtime.toISOString().slice(0, 10)})`,
45
+ });
46
+ } else {
47
+ checks.push({ name: 'context.md', status: 'warn', detail: 'Run: npx contextforge generate' });
48
+ }
49
+
50
+ try {
51
+ await fs.access(root, fs.constants.W_OK);
52
+ checks.push({ name: 'Write permission', status: 'ok', detail: root });
53
+ } catch {
54
+ checks.push({ name: 'Write permission', status: 'fail', detail: 'Cannot write to project root' });
55
+ ok = false;
56
+ }
57
+
58
+ const envCheck = await validateEnvSetup(root);
59
+ if (await fs.pathExists(path.join(root, '.env'))) {
60
+ checks.push({
61
+ name: '.env file',
62
+ status: 'ok',
63
+ detail: hasAnyAiKey() ? 'API key(s) present' : 'No API keys set (AI disabled)',
64
+ });
65
+ } else {
66
+ checks.push({
67
+ name: '.env file',
68
+ status: 'warn',
69
+ detail: 'Copy .env.example to .env for AI enrichment',
70
+ });
71
+ }
72
+
73
+ if (envCheck.hasOpenAI) checks.push({ name: 'OpenAI key', status: 'ok', detail: 'Configured' });
74
+ if (envCheck.hasGroq) checks.push({ name: 'Groq key', status: 'ok', detail: 'Configured' });
75
+
76
+ try {
77
+ const { default: simpleGit } = await import('simple-git');
78
+ const git = simpleGit(root);
79
+ if (await git.checkIsRepo()) {
80
+ checks.push({ name: 'Git', status: 'ok', detail: `Branch: ${(await git.branch()).current}` });
81
+ } else {
82
+ checks.push({ name: 'Git', status: 'warn', detail: 'Not a git repo — history skipped' });
83
+ }
84
+ } catch {
85
+ checks.push({
86
+ name: 'Git',
87
+ status: 'warn',
88
+ detail: 'Git not in PATH — install Git for commit insights',
89
+ });
90
+ }
91
+
92
+ const config = await loadConfig(root);
93
+ if (config.ai?.enabled) {
94
+ if (isAiAvailable(config)) {
95
+ checks.push({
96
+ name: 'AI enrichment',
97
+ status: 'ok',
98
+ detail: `Provider: ${config.ai.provider}, model: ${config.ai.openai?.model || config.ai.groq?.model}`,
99
+ });
100
+ } else {
101
+ checks.push({
102
+ name: 'AI enrichment',
103
+ status: 'fail',
104
+ detail: 'Enabled in config but no valid API keys',
105
+ });
106
+ ok = false;
107
+ }
108
+ } else {
109
+ checks.push({ name: 'AI enrichment', status: 'ok', detail: 'Disabled (enable in config + .env)' });
110
+ }
111
+
112
+ if (isAutoStartEnabledViaEnv()) {
113
+ const pidPath = getPidPath(root);
114
+ const watcherRunning = await fs.pathExists(pidPath);
115
+ checks.push({
116
+ name: 'npm install auto-start',
117
+ status: 'ok',
118
+ detail: watcherRunning
119
+ ? 'Enabled — background watch is running'
120
+ : 'Enabled — runs on next npm install (or npm run watch)',
121
+ });
122
+ } else {
123
+ checks.push({
124
+ name: 'npm install auto-start',
125
+ status: 'ok',
126
+ detail: 'Disabled (CONTEXTFORGE_AUTO_START=false or SKIP_POSTINSTALL=1)',
127
+ });
128
+ }
129
+
130
+ const intervalMin = config.refreshIntervalMinutes ?? 0;
131
+ if (intervalMin > 0 && isPostinstallWatchEnabledViaEnv()) {
132
+ checks.push({
133
+ name: 'Watch auto-refresh',
134
+ status: 'ok',
135
+ detail: `Every ${intervalMin} minute(s) (from .env or config)`,
136
+ });
137
+ } else {
138
+ checks.push({
139
+ name: 'Watch auto-refresh',
140
+ status: 'ok',
141
+ detail: 'Off — set CONTEXTFORGE_REFRESH_INTERVAL_MINUTES in .env or watch --interval',
142
+ });
143
+ }
144
+
145
+ console.log('\nContextForge Doctor\n');
146
+ for (const c of checks) {
147
+ const icon = c.status === 'ok' ? '✓' : c.status === 'warn' ? '!' : '✗';
148
+ console.log(` ${icon} ${c.name}: ${c.detail}`);
149
+ }
150
+ console.log(ok ? '\nAll critical checks passed.\n' : '\nSome checks failed — fix above issues.\n');
151
+ if (!ok) process.exit(1);
152
+ }
@@ -0,0 +1,7 @@
1
+ import { ensureInitialized } from '../../core/ensure-setup.js';
2
+ import { runPipeline } from '../../core/pipeline.js';
3
+
4
+ export async function generateCommand(projectRoot, options = {}) {
5
+ await ensureInitialized(projectRoot, { enableAi: options.forceAi });
6
+ await runPipeline(projectRoot, options);
7
+ }
@@ -0,0 +1,98 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'node:path';
3
+ import {
4
+ writeDefaultConfig,
5
+ isInitialized,
6
+ loadConfig,
7
+ syncConfigFile,
8
+ } from '../../core/config.js';
9
+ import { runPipeline } from '../../core/pipeline.js';
10
+ import { getContextForgeRoot } from '../../core/paths.js';
11
+ import { ensureEnvExample, ensureEnvFromExample, validateEnvSetup } from '../../core/setup-env.js';
12
+ import { isAiAvailable } from '../../services/ai/index.js';
13
+ import { loadProjectEnv } from '../../core/env.js';
14
+ import { ensureProjectPrompts } from '../../core/setup-prompts.js';
15
+
16
+ export async function initCommand(projectRoot, options = {}) {
17
+ const initialized = await isInitialized(projectRoot);
18
+
19
+ if (initialized && !options.force) {
20
+ console.log('ContextForge already initialized.');
21
+ console.log(` → ${getContextForgeRoot(projectRoot)}`);
22
+ console.log('Run: npx contextforge generate');
23
+ return;
24
+ }
25
+
26
+ const initOptions = {};
27
+ if (options.enableAi) {
28
+ initOptions.ai = { enabled: true, enrichContext: true, background: true };
29
+ }
30
+
31
+ await writeDefaultConfig(projectRoot, initOptions);
32
+ const cfgWritten = await syncConfigFile(projectRoot, {
33
+ enableAi: options.enableAi,
34
+ upgrade: options.force || !initialized,
35
+ });
36
+ const { targetDir: promptsDir, copied: promptsCopied } =
37
+ await ensureProjectPrompts(projectRoot);
38
+ await ensureEnvExample(projectRoot);
39
+ const createdEnv = await ensureEnvFromExample(projectRoot);
40
+
41
+ console.log('Initialized ContextForge.');
42
+ console.log(` → ${getContextForgeRoot(projectRoot)}/config.json`);
43
+ console.log(` → ${promptsDir}/ (${promptsCopied} prompt file(s) copied — edit to customize AI)`);
44
+ if (createdEnv) {
45
+ console.log(' → .env created from .env.example — add your API keys');
46
+ } else {
47
+ console.log(' → Copy .env.example to .env and set OPENAI_API_KEY / GROQ_API_KEY');
48
+ }
49
+
50
+ await loadProjectEnv(projectRoot);
51
+ await runPipeline(projectRoot, {
52
+ verbose: options.verbose,
53
+ forceAi: options.enableAi,
54
+ });
55
+
56
+ const cfg = await loadConfig(projectRoot);
57
+
58
+ if (cfg.installCursorRules !== false) {
59
+ console.log(' → Cursor rule: .cursor/rules/contextforge.mdc');
60
+ }
61
+ if (cfg.watch && cfg.autoGenerate) {
62
+ console.log('\nWatch mode: `npx contextforge watch`');
63
+ console.log(' • Updates when you save files (content hash detection)');
64
+ if (cfg.refreshIntervalMinutes > 0) {
65
+ console.log(
66
+ ` • Auto-refresh every ${cfg.refreshIntervalMinutes} min (from .env or config)`
67
+ );
68
+ } else {
69
+ console.log(' • Timer: set CONTEXTFORGE_REFRESH_INTERVAL_MINUTES in .env (e.g. 15)');
70
+ }
71
+ console.log(' • Change log: `npx contextforge changes` → .contextforge/CHANGES.md');
72
+ console.log(' • Daemon: `watch --daemon` then `npx contextforge stop`');
73
+ }
74
+
75
+ const envCheck = await validateEnvSetup(projectRoot);
76
+ if (cfg.ai?.enabled) {
77
+ if (isAiAvailable(cfg)) {
78
+ console.log('\nAI: enabled — OpenAI/Groq will enrich context.md (background if configured)');
79
+ if (envCheck.hasOpenAI) console.log(' → OpenAI key loaded');
80
+ if (envCheck.hasGroq) console.log(' → Groq key loaded');
81
+ } else {
82
+ console.log('\nAI: enabled in config but API keys missing in .env');
83
+ envCheck.issues.forEach((i) => console.log(` → ${i}`));
84
+ }
85
+ } else if (options.enableAi) {
86
+ envCheck.issues.forEach((i) => console.log(` → ${i}`));
87
+ }
88
+
89
+ const gitignore = projectRoot + '/.gitignore';
90
+ if (await fs.pathExists(gitignore)) {
91
+ const content = await fs.readFile(gitignore, 'utf8');
92
+ if (!content.includes('.contextforge/cache')) {
93
+ console.log('\nTip: Add to .gitignore:');
94
+ console.log(' .contextforge/cache/');
95
+ console.log(' .contextforge/logs/');
96
+ }
97
+ }
98
+ }
@@ -0,0 +1,53 @@
1
+ import fs from 'fs-extra';
2
+ import { getOutputPath, OUTPUT_FILES } from '../../core/paths.js';
3
+
4
+ export async function promptCommand(projectRoot, options = {}) {
5
+ const mdPath = getOutputPath(projectRoot, OUTPUT_FILES.contextMd);
6
+ const jsonPath = getOutputPath(projectRoot, OUTPUT_FILES.contextJson);
7
+
8
+ if (!(await fs.pathExists(mdPath))) {
9
+ console.error('No context found. Run: npx contextforge init');
10
+ process.exit(1);
11
+ }
12
+
13
+ const sections = (options.sections || 'all').split(',').map((s) => s.trim().toLowerCase());
14
+ const useAll = sections.includes('all');
15
+
16
+ if (useAll) {
17
+ const md = await fs.readFile(mdPath, 'utf8');
18
+ console.log('--- ContextForge AI Context ---\n');
19
+ console.log(md);
20
+ console.log('\n--- End Context ---');
21
+ return;
22
+ }
23
+
24
+ const snapshot = await fs.readJson(jsonPath);
25
+ const parts = [];
26
+
27
+ if (sections.includes('stack')) {
28
+ const ts = snapshot.techStack;
29
+ parts.push(
30
+ `Tech: Frontend[${ts.frontend.join(', ')}] Backend[${ts.backend.join(', ')}] DB[${ts.database.join(', ')}]`
31
+ );
32
+ }
33
+ if (sections.includes('arch')) {
34
+ parts.push(`Architecture: ${snapshot.architecture?.primary?.displayLabel}`);
35
+ if (snapshot.architecture?.apiFlow) parts.push(`API Flow: ${snapshot.architecture.apiFlow}`);
36
+ }
37
+ if (sections.includes('git') && snapshot.git?.commits?.length) {
38
+ parts.push('Recent commits:');
39
+ snapshot.git.commits.slice(0, 5).forEach((c) => {
40
+ parts.push(` ${c.hash} ${c.message.slice(0, 50)}`);
41
+ });
42
+ }
43
+ if (sections.includes('bugs') && snapshot.bugs?.issues?.length) {
44
+ parts.push('Issues:');
45
+ snapshot.bugs.issues.slice(0, 5).forEach((i) => {
46
+ parts.push(` [${i.severity}] ${i.message}`);
47
+ });
48
+ }
49
+
50
+ console.log('--- ContextForge AI Context ---\n');
51
+ console.log(parts.join('\n'));
52
+ console.log('\n--- End Context ---');
53
+ }
@@ -0,0 +1,10 @@
1
+ import { stopWatcher } from '../../watcher/watcher.js';
2
+
3
+ export async function stopCommand(projectRoot) {
4
+ try {
5
+ await stopWatcher(projectRoot);
6
+ } catch (err) {
7
+ console.error(err.message);
8
+ process.exit(1);
9
+ }
10
+ }
@@ -0,0 +1,22 @@
1
+ import fs from 'fs-extra';
2
+ import { getOutputPath, OUTPUT_FILES } from '../../core/paths.js';
3
+
4
+ export async function summaryCommand(projectRoot) {
5
+ const jsonPath = getOutputPath(projectRoot, OUTPUT_FILES.contextJson);
6
+
7
+ if (!(await fs.pathExists(jsonPath))) {
8
+ console.error('No context found. Run: npx contextforge init');
9
+ process.exit(1);
10
+ }
11
+
12
+ const snapshot = await fs.readJson(jsonPath);
13
+ const ts = snapshot.techStack;
14
+
15
+ console.log(`\n${snapshot.projectName || 'Project'} — ContextForge Summary\n`);
16
+ console.log(`Generated: ${snapshot.generatedAt}`);
17
+ console.log(`Files scanned: ${snapshot.filesScanned}`);
18
+ console.log(`\nStack: ${[...ts.frontend, ...ts.backend].join(', ') || 'unknown'}`);
19
+ console.log(`Architecture: ${snapshot.architecture?.primary?.displayLabel || 'unknown'}`);
20
+ console.log(`Database: ${[...ts.database, ...ts.orm].join(', ') || 'none detected'}`);
21
+ console.log(`\nFull context: .contextforge/context.md\n`);
22
+ }
@@ -0,0 +1,9 @@
1
+ import { ensureInitialized } from '../../core/ensure-setup.js';
2
+ import { runPipeline } from '../../core/pipeline.js';
3
+ import { startWatcher } from '../../watcher/watcher.js';
4
+
5
+ export async function watchCommand(projectRoot, options = {}) {
6
+ await ensureInitialized(projectRoot);
7
+ await runPipeline(projectRoot, { verbose: options.verbose });
8
+ await startWatcher(projectRoot, options);
9
+ }
@@ -0,0 +1,120 @@
1
+ import { Command } from 'commander';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { initCommand } from './commands/init.js';
5
+ import { generateCommand } from './commands/generate.js';
6
+ import { watchCommand } from './commands/watch.js';
7
+ import { summaryCommand } from './commands/summary.js';
8
+ import { promptCommand } from './commands/prompt.js';
9
+ import { stopCommand } from './commands/stop.js';
10
+ import { doctorCommand } from './commands/doctor.js';
11
+ import { changesCommand } from './commands/changes.js';
12
+
13
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
14
+
15
+ function resolveCwd(cwd) {
16
+ return path.resolve(cwd || process.cwd());
17
+ }
18
+
19
+ export async function runCli(argv) {
20
+ const program = new Command();
21
+
22
+ program
23
+ .name('contextforge-cli-ai-prompt-pirates')
24
+ .description('AI-ready repository context generator')
25
+ .version('0.4.0')
26
+ .option('-c, --cwd <path>', 'Project root directory', process.cwd())
27
+ .option('-v, --verbose', 'Verbose logging');
28
+
29
+ program
30
+ .command('init')
31
+ .description('Initialize .contextforge and generate initial context')
32
+ .option('-f, --force', 'Re-run setup even if already initialized')
33
+ .option('--ai', 'Enable AI enrichment in config')
34
+ .action(async (opts, cmd) => {
35
+ const globalOpts = cmd.parent?.opts?.() || {};
36
+ await initCommand(resolveCwd(globalOpts.cwd), {
37
+ force: opts.force,
38
+ verbose: globalOpts.verbose,
39
+ enableAi: opts.ai,
40
+ });
41
+ });
42
+
43
+ program
44
+ .command('generate')
45
+ .description('Scan repository and regenerate context')
46
+ .option('--ai', 'Force enable AI enrichment (OpenAI/Groq)')
47
+ .option('--no-ai', 'Skip AI enrichment for this run')
48
+ .action(async (opts, cmd) => {
49
+ const globalOpts = cmd.parent?.opts?.() || {};
50
+ await generateCommand(resolveCwd(globalOpts.cwd), {
51
+ verbose: globalOpts.verbose,
52
+ forceAi: opts.ai,
53
+ skipAi: opts.noAi,
54
+ });
55
+ });
56
+
57
+ program
58
+ .command('watch')
59
+ .description('Watch files + optional timed refresh of context.md')
60
+ .option('--daemon', 'Write PID file for contextforge stop')
61
+ .option(
62
+ '-i, --interval <minutes>',
63
+ 'Also regenerate every N minutes (e.g. 5, 15, 30)'
64
+ )
65
+ .action(async (opts, cmd) => {
66
+ const globalOpts = cmd.parent?.opts?.() || {};
67
+ const interval = opts.interval ? parseFloat(opts.interval) : undefined;
68
+ await watchCommand(resolveCwd(globalOpts.cwd), {
69
+ verbose: globalOpts.verbose,
70
+ daemon: opts.daemon,
71
+ intervalMinutes: interval,
72
+ });
73
+ });
74
+
75
+ program
76
+ .command('changes')
77
+ .description('Show history of what changed between runs')
78
+ .option('-n, --limit <number>', 'Number of runs to show', '10')
79
+ .action(async (opts, cmd) => {
80
+ const globalOpts = cmd.parent?.opts?.() || {};
81
+ await changesCommand(resolveCwd(globalOpts.cwd), {
82
+ limit: parseInt(opts.limit, 10),
83
+ });
84
+ });
85
+
86
+ program
87
+ .command('stop')
88
+ .description('Stop background watcher started with watch --daemon')
89
+ .action(async (_opts, cmd) => {
90
+ const globalOpts = cmd.parent?.opts?.() || {};
91
+ await stopCommand(resolveCwd(globalOpts.cwd));
92
+ });
93
+
94
+ program
95
+ .command('doctor')
96
+ .description('Health check: Node, Git, .env, AI keys, context.md')
97
+ .action(async (_opts, cmd) => {
98
+ const globalOpts = cmd.parent?.opts?.() || {};
99
+ await doctorCommand(resolveCwd(globalOpts.cwd));
100
+ });
101
+
102
+ program
103
+ .command('summary')
104
+ .description('Print short project summary to stdout')
105
+ .action(async (_opts, cmd) => {
106
+ const globalOpts = cmd.parent?.opts?.() || {};
107
+ await summaryCommand(resolveCwd(globalOpts.cwd));
108
+ });
109
+
110
+ program
111
+ .command('prompt')
112
+ .description('Export AI-ready context block to stdout')
113
+ .option('-s, --sections <list>', 'Comma-separated sections: stack,arch,git,bugs,all', 'all')
114
+ .action(async (opts, cmd) => {
115
+ const globalOpts = cmd.parent?.opts?.() || {};
116
+ await promptCommand(resolveCwd(globalOpts.cwd), { sections: opts.sections });
117
+ });
118
+
119
+ await program.parseAsync(argv);
120
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Per-section confidence for context.md accuracy labels.
3
+ * @param {object} snapshot
4
+ */
5
+ export function computeConfidenceScores(snapshot) {
6
+ const ts = snapshot.techStack || {};
7
+ const scores = {};
8
+
9
+ scores.techStack =
10
+ ts.signals?.length >= 3 ? 'high' : ts.signals?.length >= 1 ? 'medium' : 'low';
11
+
12
+ scores.architecture = snapshot.architecture?.primary?.confidence || 'low';
13
+
14
+ scores.database =
15
+ snapshot.database?.models?.length > 0
16
+ ? 'high'
17
+ : snapshot.database?.orm
18
+ ? 'medium'
19
+ : 'low';
20
+
21
+ scores.git = snapshot.git?.available && snapshot.git?.commits?.length ? 'high' : 'low';
22
+
23
+ scores.apiRoutes =
24
+ snapshot.codeInsights?.apiEndpoints?.length > 0 ||
25
+ snapshot.ast?.routes?.length > 0
26
+ ? 'high'
27
+ : 'low';
28
+
29
+ scores.bugs =
30
+ snapshot.bugs?.issues?.some((i) => i.type === 'eslint')
31
+ ? 'high'
32
+ : snapshot.bugs?.issues?.length
33
+ ? 'medium'
34
+ : 'low';
35
+
36
+ scores.overall = aggregateOverall(scores);
37
+
38
+ return scores;
39
+ }
40
+
41
+ function aggregateOverall(scores) {
42
+ const vals = Object.values(scores).filter((v) => v !== scores.overall);
43
+ if (vals.every((v) => v === 'high')) return 'high';
44
+ if (vals.some((v) => v === 'low') && !vals.some((v) => v === 'high')) return 'low';
45
+ return 'medium';
46
+ }
47
+
48
+ export function confidenceBadge(level) {
49
+ const map = { high: '🟢 High', medium: '🟡 Medium', low: '🔴 Low' };
50
+ return map[level] || level;
51
+ }