@stackbilt/cli 0.1.5 → 0.1.8

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.
@@ -154,7 +154,7 @@ function printReport(report) {
154
154
  }
155
155
  function getRecentCommits(count) {
156
156
  try {
157
- const log = (0, node_child_process_1.execSync)(`git log -${count} --format='%H|%an|%aI|%B---END---' --name-only 2>/dev/null`, { encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024 });
157
+ const log = runGit(['log', `-${count}`, '--format=%H|%an|%aI|%B---END---', '--name-only']);
158
158
  const commits = [];
159
159
  const entries = log.split('---END---');
160
160
  for (const entry of entries) {
@@ -165,7 +165,7 @@ function getRecentCommits(count) {
165
165
  if (!firstLine.includes('|'))
166
166
  continue;
167
167
  const pipeIdx = firstLine.indexOf('|');
168
- const sha = firstLine.slice(0, pipeIdx).replace(/'/g, '');
168
+ const sha = firstLine.slice(0, pipeIdx);
169
169
  const rest = firstLine.slice(pipeIdx + 1);
170
170
  const [author, timestamp, ...msgParts] = rest.split('|');
171
171
  const files = [];
@@ -189,4 +189,10 @@ function getRecentCommits(count) {
189
189
  return [];
190
190
  }
191
191
  }
192
- //# sourceMappingURL=audit.js.map
192
+ function runGit(args) {
193
+ return (0, node_child_process_1.execFileSync)('git', args, {
194
+ encoding: 'utf-8',
195
+ maxBuffer: 10 * 1024 * 1024,
196
+ });
197
+ }
198
+ //# sourceMappingURL=audit.js.map
@@ -107,17 +107,22 @@ async function setupCommand(options, args) {
107
107
  console.log(JSON.stringify(result, null, 2));
108
108
  return index_2.EXIT_CODE.SUCCESS;
109
109
  }
110
- console.log(' Charter setup complete.');
111
- console.log(` Config path: ${result.configPath}`);
112
- console.log(` .charter initialized: ${result.initialized ? 'yes' : 'already present'}`);
110
+ console.log(' Governance guardrails are now active for this repo.');
111
+ console.log(` Baseline path: ${result.configPath}`);
112
+ console.log(` Baseline created: ${result.initialized ? 'yes' : 'already present'}`);
113
113
  if (result.workflow.mode === 'github') {
114
- console.log(` GitHub workflow: ${result.workflow.created ? 'created' : 'already present'} (${result.workflow.path})`);
114
+ console.log(` CI policy gate: ${result.workflow.created ? 'enabled' : 'already present'} (${result.workflow.path})`);
115
115
  }
116
116
  console.log('');
117
- console.log(' Next steps:');
118
- console.log(' 1. Run: charter validate --format text');
119
- console.log(' 2. Run: charter drift --format text');
120
- console.log(' 3. Tune .charter/config.json and patterns/*.json');
117
+ console.log(' What this gives you immediately:');
118
+ console.log(' - Merge-time checks for risky changes without governance links');
119
+ console.log(' - Drift detection against your blessed stack');
120
+ console.log(' - Audit-ready governance evidence from repo history');
121
+ console.log('');
122
+ console.log(' Run now:');
123
+ console.log(' 1. charter validate --format text');
124
+ console.log(' 2. charter drift --format text');
125
+ console.log(' 3. charter audit --format text');
121
126
  return index_2.EXIT_CODE.SUCCESS;
122
127
  }
123
128
  function writeFileIfMissing(targetPath, content, force) {
@@ -137,4 +142,4 @@ function getFlag(args, flag) {
137
142
  }
138
143
  return undefined;
139
144
  }
140
- //# sourceMappingURL=setup.js.map
145
+ //# sourceMappingURL=setup.js.map
@@ -124,14 +124,18 @@ function getCommitRange(args) {
124
124
  return args[rangeIdx + 1];
125
125
  }
126
126
  try {
127
- const mainBranch = (0, node_child_process_1.execSync)('git rev-parse --verify main 2>/dev/null || git rev-parse --verify master 2>/dev/null', {
128
- encoding: 'utf-8',
129
- }).trim();
130
- const currentBranch = (0, node_child_process_1.execSync)('git rev-parse HEAD', { encoding: 'utf-8' }).trim();
131
- if (mainBranch === currentBranch) {
127
+ const currentBranch = runGit(['rev-parse', 'HEAD']).trim();
128
+ let baseBranch = '';
129
+ try {
130
+ baseBranch = runGit(['rev-parse', '--verify', 'main']).trim();
131
+ }
132
+ catch {
133
+ baseBranch = runGit(['rev-parse', '--verify', 'master']).trim();
134
+ }
135
+ if (!baseBranch || baseBranch === currentBranch) {
132
136
  return 'HEAD~5..HEAD';
133
137
  }
134
- return `${mainBranch}..HEAD`;
138
+ return `${baseBranch}..HEAD`;
135
139
  }
136
140
  catch {
137
141
  return 'HEAD~5..HEAD';
@@ -139,7 +143,7 @@ function getCommitRange(args) {
139
143
  }
140
144
  function getGitCommits(range) {
141
145
  try {
142
- const log = (0, node_child_process_1.execSync)(`git log ${range} --format='%H|%an|%aI|%s' --name-only`, { encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024 });
146
+ const log = runGit(['log', range, '--format=%H|%an|%aI|%s', '--name-only']);
143
147
  const commits = [];
144
148
  let current = null;
145
149
  for (const line of log.split('\n')) {
@@ -148,7 +152,7 @@ function getGitCommits(range) {
148
152
  commits.push(current);
149
153
  const [sha, author, timestamp, ...msgParts] = line.split('|');
150
154
  current = {
151
- sha: sha.replace(/'/g, ''),
155
+ sha,
152
156
  author,
153
157
  timestamp,
154
158
  message: msgParts.join('|'),
@@ -167,4 +171,10 @@ function getGitCommits(range) {
167
171
  return [];
168
172
  }
169
173
  }
170
- //# sourceMappingURL=validate.js.map
174
+ function runGit(args) {
175
+ return (0, node_child_process_1.execFileSync)('git', args, {
176
+ encoding: 'utf-8',
177
+ maxBuffer: 10 * 1024 * 1024,
178
+ });
179
+ }
180
+ //# sourceMappingURL=validate.js.map
@@ -0,0 +1,3 @@
1
+ import type { CLIOptions } from '../index';
2
+ export declare function quickstartCommand(options: CLIOptions): Promise<number>;
3
+ export declare function whyCommand(options: CLIOptions): Promise<number>;
@@ -0,0 +1,168 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.quickstartCommand = quickstartCommand;
4
+ exports.whyCommand = whyCommand;
5
+ const node_child_process_1 = require("node:child_process");
6
+ const fs = require("node:fs");
7
+ const path = require("node:path");
8
+ const index_1 = require("../index");
9
+ const git_1 = require("@stackbilt/git");
10
+ async function quickstartCommand(options) {
11
+ const snapshot = getSnapshot(options.configPath);
12
+ if (options.format === 'json') {
13
+ console.log(JSON.stringify(snapshot, null, 2));
14
+ return index_1.EXIT_CODE.SUCCESS;
15
+ }
16
+ console.log('');
17
+ console.log(' Charter Quickstart');
18
+ console.log(' Turns governance from abstract policy into merge-time guardrails.');
19
+ console.log('');
20
+ console.log(' Repo snapshot:');
21
+ console.log(` - Git repo: ${snapshot.inGitRepo ? 'yes' : 'no'}`);
22
+ console.log(` - Governance baseline (.charter): ${snapshot.hasBaseline ? 'installed' : 'missing'}`);
23
+ console.log(` - Recent commits scanned: ${snapshot.commitsScanned}`);
24
+ console.log(` - Governance-linked commit coverage: ${snapshot.coveragePercent}%`);
25
+ console.log(` - High-risk commits without governance links: ${snapshot.highRiskUnlinked}`);
26
+ console.log('');
27
+ console.log(' Why teams use Charter:');
28
+ console.log(' - Catch risky, unreviewed changes before merge');
29
+ console.log(' - Create an auditable trail from code changes to governance decisions');
30
+ console.log(' - Keep policy checks consistent across local dev and CI');
31
+ console.log('');
32
+ console.log(` Next action: ${snapshot.nextAction}`);
33
+ console.log('');
34
+ return index_1.EXIT_CODE.SUCCESS;
35
+ }
36
+ async function whyCommand(options) {
37
+ if (options.format === 'json') {
38
+ console.log(JSON.stringify({
39
+ problem: 'Teams lose context on risky changes and approvals become inconsistent.',
40
+ charterSolves: [
41
+ 'Enforces governance trailers for significant changes',
42
+ 'Scans for stack drift against blessed patterns',
43
+ 'Produces repeatable audit evidence for PRs and reviews',
44
+ ],
45
+ value: [
46
+ 'Lower probability of breaking changes landing without architectural context',
47
+ 'Faster reviews because reviewers see linked decisions in commit metadata',
48
+ 'Clear governance posture for leadership and compliance reporting',
49
+ ],
50
+ start: 'charter setup --ci github',
51
+ }, null, 2));
52
+ return index_1.EXIT_CODE.SUCCESS;
53
+ }
54
+ console.log('');
55
+ console.log(' Why Charter');
56
+ console.log('');
57
+ console.log(' Problem it solves:');
58
+ console.log(' Governance intent lives in docs, but risky changes merge without clear decision links.');
59
+ console.log('');
60
+ console.log(' What Charter does:');
61
+ console.log(' - Enforces commit-level governance links for significant changes');
62
+ console.log(' - Detects drift from your blessed architecture patterns');
63
+ console.log(' - Generates audit output leadership and reviewers can actually use');
64
+ console.log('');
65
+ console.log(' Expected payoff:');
66
+ console.log(' - Fewer high-impact surprises in production');
67
+ console.log(' - Faster review cycles with clearer architectural accountability');
68
+ console.log(' - Consistent governance behavior across repos');
69
+ console.log('');
70
+ console.log(' Start here: charter setup --ci github');
71
+ console.log('');
72
+ return index_1.EXIT_CODE.SUCCESS;
73
+ }
74
+ function getSnapshot(configPath) {
75
+ const inGitRepo = isGitRepo();
76
+ const hasBaseline = fs.existsSync(path.join(configPath, 'config.json'));
77
+ if (!inGitRepo) {
78
+ return {
79
+ inGitRepo,
80
+ hasBaseline,
81
+ commitsScanned: 0,
82
+ coveragePercent: 0,
83
+ highRiskUnlinked: 0,
84
+ nextAction: 'Run this inside a git repository, then run: charter setup --ci github',
85
+ };
86
+ }
87
+ const commits = hasCommits() ? getRecentCommits(20) : [];
88
+ const parsed = (0, git_1.parseAllTrailers)(commits);
89
+ const linked = new Set();
90
+ for (const t of parsed.governedBy)
91
+ linked.add(t.commitSha);
92
+ for (const t of parsed.resolvesRequest)
93
+ linked.add(t.commitSha);
94
+ let highRiskUnlinked = 0;
95
+ for (const commit of commits) {
96
+ if (!linked.has(commit.sha) && (0, git_1.assessCommitRisk)(commit.files_changed, commit.message) === 'HIGH') {
97
+ highRiskUnlinked++;
98
+ }
99
+ }
100
+ const coveragePercent = commits.length > 0 ? Math.round((linked.size / commits.length) * 100) : 0;
101
+ const nextAction = !hasBaseline
102
+ ? 'Run: charter setup --ci github'
103
+ : highRiskUnlinked > 0
104
+ ? 'Run: charter validate --format text and add Governed-By trailers to high-risk commits'
105
+ : 'Run: charter audit --format text for a shareable governance posture report';
106
+ return {
107
+ inGitRepo,
108
+ hasBaseline,
109
+ commitsScanned: commits.length,
110
+ coveragePercent,
111
+ highRiskUnlinked,
112
+ nextAction,
113
+ };
114
+ }
115
+ function isGitRepo() {
116
+ try {
117
+ (0, node_child_process_1.execFileSync)('git', ['rev-parse', '--is-inside-work-tree'], { stdio: 'ignore' });
118
+ return true;
119
+ }
120
+ catch {
121
+ return false;
122
+ }
123
+ }
124
+ function getRecentCommits(count) {
125
+ try {
126
+ const log = (0, node_child_process_1.execFileSync)('git', ['log', `-${count}`, '--format=%H|%s', '--name-only'], {
127
+ encoding: 'utf-8',
128
+ maxBuffer: 10 * 1024 * 1024,
129
+ stdio: ['ignore', 'pipe', 'ignore'],
130
+ });
131
+ const commits = [];
132
+ let current = null;
133
+ for (const line of log.split('\n')) {
134
+ if (line.includes('|') && line.length > 40) {
135
+ if (current)
136
+ commits.push(current);
137
+ const [sha, ...message] = line.split('|');
138
+ current = {
139
+ sha,
140
+ author: '',
141
+ timestamp: '',
142
+ message: message.join('|'),
143
+ files_changed: [],
144
+ };
145
+ }
146
+ else if (line.trim() && current) {
147
+ current.files_changed.push(line.trim());
148
+ }
149
+ }
150
+ if (current)
151
+ commits.push(current);
152
+ return commits;
153
+ }
154
+ catch {
155
+ return [];
156
+ }
157
+ }
158
+ function hasCommits() {
159
+ try {
160
+ (0, node_child_process_1.execFileSync)('git', ['rev-parse', '--verify', 'HEAD'], {
161
+ stdio: 'ignore',
162
+ });
163
+ return true;
164
+ }
165
+ catch {
166
+ return false;
167
+ }
168
+ }
package/dist/index.js CHANGED
@@ -15,18 +15,21 @@ const validate_1 = require("./commands/validate");
15
15
  const audit_1 = require("./commands/audit");
16
16
  const drift_1 = require("./commands/drift");
17
17
  const classify_1 = require("./commands/classify");
18
+ const why_1 = require("./commands/why");
18
19
  const package_json_1 = require("../package.json");
19
20
  const CLI_VERSION = package_json_1.version;
20
21
  const HELP = `
21
22
  charter - repo-level governance toolkit
22
23
 
23
24
  Usage:
25
+ charter Show immediate governance value + risk snapshot
24
26
  charter setup [--ci github] Bootstrap .charter/ and optional CI workflow
25
27
  charter init Scaffold .charter/ config directory
26
28
  charter validate Validate git commits for governance trailers
27
29
  charter audit Generate governance audit report
28
30
  charter drift [--path <dir>] Scan files for pattern drift
29
31
  charter classify <subject> Classify a change (SURFACE/LOCAL/CROSS_CUTTING)
32
+ charter why Explain why teams adopt Charter and expected ROI
30
33
  charter doctor Check CLI + config health
31
34
  charter --help Show this help
32
35
  charter --version Show version
@@ -52,7 +55,7 @@ class CLIError extends Error {
52
55
  }
53
56
  exports.CLIError = CLIError;
54
57
  async function run(args) {
55
- if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
58
+ if (args.includes('--help') || args.includes('-h')) {
56
59
  console.log(HELP);
57
60
  return exports.EXIT_CODE.SUCCESS;
58
61
  }
@@ -60,18 +63,21 @@ async function run(args) {
60
63
  console.log(`charter v${CLI_VERSION}`);
61
64
  return exports.EXIT_CODE.SUCCESS;
62
65
  }
63
- const command = args[0];
64
- const restArgs = args.slice(1);
65
- const format = getFlag(restArgs, '--format') || 'text';
66
+ const format = getFlag(args, '--format') || 'text';
66
67
  if (format !== 'text' && format !== 'json') {
67
68
  throw new CLIError(`Invalid --format value: ${format}. Use text or json.`);
68
69
  }
69
70
  const options = {
70
- configPath: getFlag(restArgs, '--config') || '.charter',
71
+ configPath: getFlag(args, '--config') || '.charter',
71
72
  format,
72
- ciMode: restArgs.includes('--ci'),
73
- yes: restArgs.includes('--yes'),
73
+ ciMode: args.includes('--ci'),
74
+ yes: args.includes('--yes'),
74
75
  };
76
+ if (args.length === 0 || args[0].startsWith('-')) {
77
+ return (0, why_1.quickstartCommand)(options);
78
+ }
79
+ const command = args[0];
80
+ const restArgs = args.slice(1);
75
81
  switch (command) {
76
82
  case 'setup':
77
83
  return (0, setup_1.setupCommand)(options, restArgs);
@@ -85,6 +91,8 @@ async function run(args) {
85
91
  return (0, drift_1.driftCommand)(options, restArgs);
86
92
  case 'classify':
87
93
  return (0, classify_1.classifyCommand)(options, restArgs);
94
+ case 'why':
95
+ return (0, why_1.whyCommand)(options);
88
96
  case 'doctor':
89
97
  return (0, doctor_1.doctorCommand)(options);
90
98
  default:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackbilt/cli",
3
- "version": "0.1.5",
3
+ "version": "0.1.8",
4
4
  "description": "Charter CLI — repo-level governance checks",
5
5
  "bin": {
6
6
  "charter": "./dist/bin.js"