@grainulation/wheat 1.0.2 → 1.0.3

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.
@@ -49,7 +49,7 @@ function loadJSON(filePath) {
49
49
 
50
50
  /**
51
51
  * Batch git queries for all sprint files at once.
52
- * Two git calls total instead of 2 per sprint (20x faster for 16 sprints).
52
+ * One git call total instead of 2 per sprint (30x+ faster for 16 sprints).
53
53
  * Returns Map<filePath, { date: string|null, count: number }>
54
54
  */
55
55
  let _gitCache = null;
@@ -69,44 +69,36 @@ function batchGitInfo(filePaths) {
69
69
 
70
70
  if (filePaths.length === 0) { _gitCache = info; return info; }
71
71
 
72
- // Batch 1: last commit date per file
73
- // git log outputs: date line, blank line, filename(s), blank line, ...
74
- // First occurrence of each file = most recent commit
72
+ // Single git call: get dates AND counts from one log traversal.
73
+ // Format: "COMMIT <date>" header per commit, then --name-only lists files.
74
+ // First occurrence of each file gives its last-commit date.
75
+ // Total occurrences per file gives its commit count.
75
76
  try {
76
77
  const result = execFileSync('git', [
77
- 'log', '--format=%aI', '--name-only', '--diff-filter=ACMR',
78
+ 'log', '--format=COMMIT %aI', '--name-only',
78
79
  '--', ...relPaths
79
80
  ], { cwd: ROOT, timeout: 10000, stdio: ['ignore', 'pipe', 'pipe'] });
80
- const lines = result.toString().trim().split('\n');
81
- const seen = new Set();
81
+ const lines = result.toString().split('\n');
82
+ const seenForDate = new Set();
82
83
  let currentDate = null;
83
84
  for (const line of lines) {
84
85
  const trimmed = line.trim();
85
- if (!trimmed) continue; // skip blank lines (git puts them between date and filename)
86
- if (/^\d{4}-/.test(trimmed)) {
87
- currentDate = trimmed;
88
- } else if (currentDate && !seen.has(trimmed)) {
89
- seen.add(trimmed);
86
+ if (!trimmed) continue;
87
+ if (trimmed.startsWith('COMMIT ')) {
88
+ currentDate = trimmed.slice(7);
89
+ } else {
90
90
  const orig = relToOrig.get(trimmed);
91
- if (orig) info.get(orig).date = currentDate;
91
+ if (orig) {
92
+ const entry = info.get(orig);
93
+ entry.count++;
94
+ if (!seenForDate.has(trimmed)) {
95
+ seenForDate.add(trimmed);
96
+ entry.date = currentDate;
97
+ }
98
+ }
92
99
  }
93
100
  }
94
- } catch { /* git unavailable, dates stay null */ }
95
-
96
- // Batch 2: commit counts per file (count filename occurrences in log)
97
- try {
98
- const result = execFileSync('git', [
99
- 'log', '--format=', '--name-only',
100
- '--', ...relPaths
101
- ], { cwd: ROOT, timeout: 10000, stdio: ['ignore', 'pipe', 'pipe'] });
102
- const lines = result.toString().split('\n');
103
- for (const line of lines) {
104
- const trimmed = line.trim();
105
- if (!trimmed) continue;
106
- const orig = relToOrig.get(trimmed);
107
- if (orig) info.get(orig).count++;
108
- }
109
- } catch { /* counts stay 0 */ }
101
+ } catch { /* git unavailable, dates stay null, counts stay 0 */ }
110
102
 
111
103
  _gitCache = info;
112
104
  return info;
@@ -274,7 +266,7 @@ export function detectSprints(rootDir) {
274
266
  resetGitCache();
275
267
  const roots = findSprintRoots();
276
268
 
277
- // Batch all git queries upfront: 2 git calls instead of 2 per sprint
269
+ // Batch all git queries upfront: 1 git call instead of 2 per sprint
278
270
  batchGitInfo(roots.map(r => r.claimsPath));
279
271
 
280
272
  const sprints = roots.map(analyzeSprint).filter(Boolean);
@@ -597,7 +597,7 @@ function generateManifest(compilation, dir, sprintsInfo) {
597
597
  * @param {string|null} outputPath - Path to write compilation.json (null = default from config)
598
598
  * @returns {object} The compiled output object
599
599
  */
600
- function compile(inputPath, outputPath, dir) {
600
+ function compile(inputPath, outputPath, dir, opts = {}) {
601
601
  const compilerVersion = '0.2.0';
602
602
  const baseDir = dir || TARGET_DIR;
603
603
  const claimsPath = inputPath || path.join(baseDir, config.compiler.claims);
@@ -661,25 +661,28 @@ function compile(inputPath, outputPath, dir) {
661
661
 
662
662
  // ── Sprint detection (git-based, non-fatal) ──────────────────────────────
663
663
  let sprintsInfo = { active: null, sprints: [] };
664
- try {
665
- sprintsInfo = detectSprints(baseDir);
666
- } catch (err) {
667
- // Non-fatal: sprint detection failure should not block compilation
668
- console.error(`Warning: sprint detection failed — ${err.message}`);
669
- }
664
+ let sprintSummaries = [];
665
+ if (!opts.skipSprintDetection) {
666
+ try {
667
+ sprintsInfo = detectSprints(baseDir);
668
+ } catch (err) {
669
+ // Non-fatal: sprint detection failure should not block compilation
670
+ console.error(`Warning: sprint detection failed — ${err.message}`);
671
+ }
670
672
 
671
- // Build sprint summaries: active sprint gets full compilation, others get summary entries
672
- const sprintSummaries = sprintsInfo.sprints.map(s => ({
673
- name: s.name,
674
- path: s.path,
675
- status: s.status,
676
- phase: s.phase,
677
- question: s.question,
678
- claims_count: s.claims_count,
679
- active_claims: s.active_claims,
680
- last_git_activity: s.last_git_activity,
681
- git_commit_count: s.git_commit_count,
682
- }));
673
+ // Build sprint summaries: active sprint gets full compilation, others get summary entries
674
+ sprintSummaries = sprintsInfo.sprints.map(s => ({
675
+ name: s.name,
676
+ path: s.path,
677
+ status: s.status,
678
+ phase: s.phase,
679
+ question: s.question,
680
+ claims_count: s.claims_count,
681
+ active_claims: s.active_claims,
682
+ last_git_activity: s.last_git_activity,
683
+ git_commit_count: s.git_commit_count,
684
+ }));
685
+ }
683
686
 
684
687
  const compilation = {
685
688
  compiled_at: new Date().toISOString(), // Non-deterministic metadata (excluded from certificate)
@@ -718,7 +721,9 @@ function compile(inputPath, outputPath, dir) {
718
721
 
719
722
  // Generate topic-map manifest (wheat-manifest.json)
720
723
  // Pass sprintsInfo to avoid re-running detectSprints in manifest generator
721
- generateManifest(compilation, baseDir, sprintsInfo);
724
+ if (!opts.skipSprintDetection) {
725
+ generateManifest(compilation, baseDir, sprintsInfo);
726
+ }
722
727
 
723
728
  return compilation;
724
729
  }
@@ -784,6 +789,7 @@ Usage:
784
789
 
785
790
  Options:
786
791
  --dir <path> Resolve all paths relative to <path> instead of script location
792
+ --quiet, -q One-liner output (for scripts and AI agents)
787
793
  --help, -h Show this help message
788
794
  --json Output as JSON (works with --summary, --check, --gate, --scan, --next)`);
789
795
  process.exit(0);
@@ -860,8 +866,31 @@ if (outputIdx !== -1 && args[outputIdx + 1]) {
860
866
  outputPath = path.resolve(args[outputIdx + 1]);
861
867
  }
862
868
 
863
- const compilation = compile(inputPath, outputPath);
864
869
  const jsonFlag = args.includes('--json');
870
+ const quietFlag = args.includes('--quiet') || args.includes('-q');
871
+ const compilation = compile(inputPath, outputPath, undefined, { skipSprintDetection: quietFlag && !args.includes('--summary') });
872
+
873
+ // --quiet / -q: one-liner output for scripts and AI agents (~13 tokens vs ~4,600)
874
+ if (quietFlag && !args.includes('--summary')) {
875
+ const c = compilation;
876
+ const conflicts = c.sprint_meta.conflicted_claims || 0;
877
+ const suffix = conflicts > 0 ? ` (${conflicts} conflicts)` : '';
878
+ const line = `wheat: compiled ${c.sprint_meta.total_claims} claims, ${Object.keys(c.coverage).length} topics${suffix}`;
879
+ if (jsonFlag) {
880
+ console.log(JSON.stringify({
881
+ status: c.status,
882
+ claims: c.sprint_meta.total_claims,
883
+ active: c.sprint_meta.active_claims,
884
+ conflicts,
885
+ topics: Object.keys(c.coverage).length,
886
+ errors: c.errors.length,
887
+ warnings: c.warnings.length,
888
+ }));
889
+ } else {
890
+ console.log(line);
891
+ }
892
+ process.exit(c.status === 'blocked' ? 1 : 0);
893
+ }
865
894
 
866
895
  if (args.includes('--summary')) {
867
896
  const c = compilation;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@grainulation/wheat",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "Research-driven development framework — structured claims, compiled evidence, deterministic output",
5
5
  "license": "MIT",
6
6
  "author": "grainulation contributors",