@stackbilt/cli 0.1.8 → 0.1.10
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/README.md +92 -27
- package/dist/commands/audit.js +47 -0
- package/dist/commands/validate.js +72 -5
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -4,38 +4,103 @@ CLI entry point for Charter Kit -- a local-first governance toolkit for software
|
|
|
4
4
|
|
|
5
5
|
> **This is the only package most users need.** One install gives you the full Charter Kit toolkit.
|
|
6
6
|
|
|
7
|
-
## Install
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
npm install -
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
7
|
+
## Install (Recommended)
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install --save-dev @stackbilt/cli
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Use with `npx` in each repository:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx charter
|
|
17
|
+
npx charter setup --ci github
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Global install is optional if you want `charter` available system-wide:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install -g @stackbilt/cli
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
This pulls in all Charter Kit packages automatically. Use `charter setup` inside each repo to scaffold governance baseline files.
|
|
27
|
+
|
|
28
|
+
For CI pipelines, install as a dev dependency:
|
|
16
29
|
|
|
17
30
|
```bash
|
|
18
31
|
npm install --save-dev @stackbilt/cli
|
|
19
32
|
```
|
|
20
33
|
|
|
21
|
-
Requires Node >= 18.
|
|
22
|
-
|
|
23
|
-
## Quick Start
|
|
24
|
-
|
|
25
|
-
```bash
|
|
26
|
-
charter
|
|
27
|
-
charter
|
|
28
|
-
charter
|
|
29
|
-
charter
|
|
30
|
-
charter
|
|
31
|
-
charter
|
|
32
|
-
|
|
34
|
+
Requires Node >= 18.
|
|
35
|
+
|
|
36
|
+
## Quick Start
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
charter # quick value/risk snapshot + next action
|
|
40
|
+
charter why # why teams adopt Charter and expected payoff
|
|
41
|
+
charter setup # bootstrap .charter/ directory + policy baseline
|
|
42
|
+
charter doctor # check CLI + config health
|
|
43
|
+
charter validate # validate commit governance trailers
|
|
44
|
+
charter drift # scan for blessed-stack drift
|
|
45
|
+
charter audit # generate governance audit report
|
|
46
|
+
charter classify "migrate auth provider"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Human Onboarding (Copy/Paste)
|
|
50
|
+
|
|
51
|
+
Run this in the target repository:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
npm install -g @stackbilt/cli
|
|
55
|
+
charter
|
|
56
|
+
charter setup --ci github
|
|
57
|
+
charter doctor --format json
|
|
58
|
+
charter validate --format text
|
|
59
|
+
charter drift --format text
|
|
60
|
+
charter audit --format text
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
This ensures people immediately see:
|
|
64
|
+
- what Charter is doing in their repo
|
|
65
|
+
- where baseline files were created (`.charter/*`)
|
|
66
|
+
- how policy checks behave before merge
|
|
67
|
+
|
|
68
|
+
## LM Agent Onboarding (Deterministic)
|
|
69
|
+
|
|
70
|
+
Use JSON output and explicit CI semantics:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
charter --format json
|
|
74
|
+
charter setup --ci github --yes --format json
|
|
75
|
+
charter doctor --format json
|
|
76
|
+
charter validate --format json --ci
|
|
77
|
+
charter drift --format json --ci
|
|
78
|
+
charter audit --format json
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Agent handling contract:
|
|
82
|
+
- `exit 0`: pass
|
|
83
|
+
- `exit 1`: policy violation (action required)
|
|
84
|
+
- `exit 2`: runtime/usage failure
|
|
33
85
|
|
|
34
86
|
## Commands
|
|
35
87
|
|
|
36
|
-
### `charter
|
|
37
|
-
|
|
38
|
-
|
|
88
|
+
### `charter` (no args)
|
|
89
|
+
|
|
90
|
+
Default first-run experience. Shows:
|
|
91
|
+
- repository governance baseline status
|
|
92
|
+
- commit governance coverage snapshot
|
|
93
|
+
- high-risk unlinked commit count
|
|
94
|
+
- one recommended next action
|
|
95
|
+
|
|
96
|
+
### `charter why`
|
|
97
|
+
|
|
98
|
+
Explains the adoption case in plain terms (problem, what Charter enforces, expected operational payoff).
|
|
99
|
+
|
|
100
|
+
### `charter setup`
|
|
101
|
+
|
|
102
|
+
Bootstrap `.charter/` with config, patterns, and policies. Optionally generates a GitHub Actions workflow.
|
|
103
|
+
This is the command that applies Charter governance into a repository.
|
|
39
104
|
|
|
40
105
|
```bash
|
|
41
106
|
charter setup --ci github --yes
|
|
@@ -69,13 +134,13 @@ charter drift --path ./src --ci
|
|
|
69
134
|
|
|
70
135
|
Generate a governance audit report covering trailers, risk, and drift.
|
|
71
136
|
|
|
72
|
-
### `charter classify`
|
|
137
|
+
### `charter classify`
|
|
73
138
|
|
|
74
139
|
Classify a change as `SURFACE`, `LOCAL`, or `CROSS_CUTTING`.
|
|
75
140
|
|
|
76
141
|
```bash
|
|
77
|
-
charter classify "update button color"
|
|
78
|
-
```
|
|
142
|
+
charter classify "update button color"
|
|
143
|
+
```
|
|
79
144
|
|
|
80
145
|
## Global Options
|
|
81
146
|
|
package/dist/commands/audit.js
CHANGED
|
@@ -91,6 +91,11 @@ function generateAuditReport(projectName, configPath, patterns) {
|
|
|
91
91
|
const patternScore = Math.min(100, activePatterns.length * 20);
|
|
92
92
|
const policyScore = Math.min(100, policyFiles.length * 33);
|
|
93
93
|
const overall = Math.round((trailerScore * 0.5) + (patternScore * 0.3) + (policyScore * 0.2));
|
|
94
|
+
const scoreInputs = {
|
|
95
|
+
coveragePercent,
|
|
96
|
+
activePatterns: activePatterns.length,
|
|
97
|
+
policyFiles: policyFiles.length,
|
|
98
|
+
};
|
|
94
99
|
return {
|
|
95
100
|
project: projectName,
|
|
96
101
|
generatedAt: new Date().toISOString(),
|
|
@@ -118,6 +123,12 @@ function generateAuditReport(projectName, configPath, patterns) {
|
|
|
118
123
|
patternDefinitions: Math.round(patternScore),
|
|
119
124
|
policyDocumentation: Math.round(policyScore),
|
|
120
125
|
},
|
|
126
|
+
criteria: {
|
|
127
|
+
trailerCoverage: 'coverage_percent * 1.5 (max 100). 67%+ coverage earns full points.',
|
|
128
|
+
patternDefinitions: 'active_pattern_count * 20 (max 100). 5+ active patterns earns full points.',
|
|
129
|
+
policyDocumentation: 'policy_markdown_files * 33 (max 100). 3+ policy files earns full points.',
|
|
130
|
+
},
|
|
131
|
+
recommendations: getRecommendations(scoreInputs),
|
|
121
132
|
},
|
|
122
133
|
};
|
|
123
134
|
}
|
|
@@ -151,6 +162,16 @@ function printReport(report) {
|
|
|
151
162
|
console.log(` Pattern definitions: ${report.score.breakdown.patternDefinitions}/100 (30% weight)`);
|
|
152
163
|
console.log(` Policy documentation: ${report.score.breakdown.policyDocumentation}/100 (20% weight)`);
|
|
153
164
|
console.log('');
|
|
165
|
+
console.log(' Scoring Criteria');
|
|
166
|
+
console.log(` - Trailer coverage: ${report.score.criteria.trailerCoverage}`);
|
|
167
|
+
console.log(` - Pattern definitions: ${report.score.criteria.patternDefinitions}`);
|
|
168
|
+
console.log(` - Policy documentation: ${report.score.criteria.policyDocumentation}`);
|
|
169
|
+
console.log('');
|
|
170
|
+
console.log(' Actionable Next Steps');
|
|
171
|
+
for (const rec of report.score.recommendations) {
|
|
172
|
+
console.log(` - ${rec}`);
|
|
173
|
+
}
|
|
174
|
+
console.log('');
|
|
154
175
|
}
|
|
155
176
|
function getRecentCommits(count) {
|
|
156
177
|
try {
|
|
@@ -193,6 +214,32 @@ function runGit(args) {
|
|
|
193
214
|
return (0, node_child_process_1.execFileSync)('git', args, {
|
|
194
215
|
encoding: 'utf-8',
|
|
195
216
|
maxBuffer: 10 * 1024 * 1024,
|
|
217
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
196
218
|
});
|
|
197
219
|
}
|
|
220
|
+
function getRecommendations(inputs) {
|
|
221
|
+
const recommendations = [];
|
|
222
|
+
const missingCoverage = Math.max(0, 67 - inputs.coveragePercent);
|
|
223
|
+
if (missingCoverage > 0) {
|
|
224
|
+
recommendations.push(`Increase governance trailer coverage by ${missingCoverage}% to reach full trailer score (add Governed-By/Resolves-Request trailers).`);
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
recommendations.push('Trailer coverage is at full-score threshold.');
|
|
228
|
+
}
|
|
229
|
+
const missingPatterns = Math.max(0, 5 - inputs.activePatterns);
|
|
230
|
+
if (missingPatterns > 0) {
|
|
231
|
+
recommendations.push(`Add ${missingPatterns} active pattern(s) in .charter/patterns/*.json to reach full pattern score (target: 5 active patterns).`);
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
recommendations.push('Pattern definitions are at full-score threshold.');
|
|
235
|
+
}
|
|
236
|
+
const missingPolicies = Math.max(0, 3 - inputs.policyFiles);
|
|
237
|
+
if (missingPolicies > 0) {
|
|
238
|
+
recommendations.push(`Add ${missingPolicies} policy markdown file(s) in .charter/policies/ to reach full policy score (target: 3 policy files).`);
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
recommendations.push('Policy documentation is at full-score threshold.');
|
|
242
|
+
}
|
|
243
|
+
return recommendations;
|
|
244
|
+
}
|
|
198
245
|
//# sourceMappingURL=audit.js.map
|
|
@@ -14,8 +14,32 @@ const git_1 = require("@stackbilt/git");
|
|
|
14
14
|
const git_2 = require("@stackbilt/git");
|
|
15
15
|
async function validateCommand(options, args) {
|
|
16
16
|
const config = (0, config_1.loadConfig)(options.configPath);
|
|
17
|
+
if (!hasCommits()) {
|
|
18
|
+
if (options.format === 'json') {
|
|
19
|
+
console.log(JSON.stringify({ status: 'PASS', summary: 'No commits to validate.' }, null, 2));
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
console.log(' No commits to validate.');
|
|
23
|
+
}
|
|
24
|
+
return index_1.EXIT_CODE.SUCCESS;
|
|
25
|
+
}
|
|
17
26
|
const range = getCommitRange(args);
|
|
18
|
-
const
|
|
27
|
+
const commitLoad = getGitCommits(range);
|
|
28
|
+
if (commitLoad.error) {
|
|
29
|
+
if (options.format === 'json') {
|
|
30
|
+
console.log(JSON.stringify({
|
|
31
|
+
status: 'ERROR',
|
|
32
|
+
summary: 'Failed to read git commits for validation.',
|
|
33
|
+
details: commitLoad.error,
|
|
34
|
+
}, null, 2));
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
console.log(' [fail] Failed to read git commits for validation.');
|
|
38
|
+
console.log(` ${commitLoad.error}`);
|
|
39
|
+
}
|
|
40
|
+
return index_1.EXIT_CODE.RUNTIME_ERROR;
|
|
41
|
+
}
|
|
42
|
+
const commits = commitLoad.commits;
|
|
19
43
|
if (commits.length === 0) {
|
|
20
44
|
if (options.format === 'json') {
|
|
21
45
|
console.log(JSON.stringify({ status: 'PASS', summary: 'No commits to validate.' }, null, 2));
|
|
@@ -123,6 +147,7 @@ function getCommitRange(args) {
|
|
|
123
147
|
if (rangeIdx !== -1 && rangeIdx + 1 < args.length) {
|
|
124
148
|
return args[rangeIdx + 1];
|
|
125
149
|
}
|
|
150
|
+
const recentRange = getRecentCommitRange();
|
|
126
151
|
try {
|
|
127
152
|
const currentBranch = runGit(['rev-parse', 'HEAD']).trim();
|
|
128
153
|
let baseBranch = '';
|
|
@@ -133,12 +158,12 @@ function getCommitRange(args) {
|
|
|
133
158
|
baseBranch = runGit(['rev-parse', '--verify', 'master']).trim();
|
|
134
159
|
}
|
|
135
160
|
if (!baseBranch || baseBranch === currentBranch) {
|
|
136
|
-
return
|
|
161
|
+
return recentRange;
|
|
137
162
|
}
|
|
138
163
|
return `${baseBranch}..HEAD`;
|
|
139
164
|
}
|
|
140
165
|
catch {
|
|
141
|
-
return
|
|
166
|
+
return recentRange;
|
|
142
167
|
}
|
|
143
168
|
}
|
|
144
169
|
function getGitCommits(range) {
|
|
@@ -165,16 +190,58 @@ function getGitCommits(range) {
|
|
|
165
190
|
}
|
|
166
191
|
if (current)
|
|
167
192
|
commits.push(current);
|
|
168
|
-
return commits;
|
|
193
|
+
return { commits };
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
return {
|
|
197
|
+
commits: [],
|
|
198
|
+
error: getGitErrorMessage(error),
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
function hasCommits() {
|
|
203
|
+
try {
|
|
204
|
+
runGit(['rev-parse', '--verify', 'HEAD']);
|
|
205
|
+
return true;
|
|
169
206
|
}
|
|
170
207
|
catch {
|
|
171
|
-
return
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
function getRecentCommitRange() {
|
|
212
|
+
try {
|
|
213
|
+
const count = Number.parseInt(runGit(['rev-list', '--count', 'HEAD']).trim(), 10);
|
|
214
|
+
if (!Number.isFinite(count) || count <= 1) {
|
|
215
|
+
return 'HEAD';
|
|
216
|
+
}
|
|
217
|
+
const span = Math.min(5, count - 1);
|
|
218
|
+
return `HEAD~${span}..HEAD`;
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
return 'HEAD';
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
function getGitErrorMessage(error) {
|
|
225
|
+
const fallback = 'Unknown git error.';
|
|
226
|
+
if (!(error instanceof Error))
|
|
227
|
+
return fallback;
|
|
228
|
+
const execError = error;
|
|
229
|
+
if (execError.stderr) {
|
|
230
|
+
const stderr = execError.stderr.toString().trim();
|
|
231
|
+
if (stderr.length > 0) {
|
|
232
|
+
return stderr;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (execError.message) {
|
|
236
|
+
return execError.message.trim();
|
|
172
237
|
}
|
|
238
|
+
return fallback;
|
|
173
239
|
}
|
|
174
240
|
function runGit(args) {
|
|
175
241
|
return (0, node_child_process_1.execFileSync)('git', args, {
|
|
176
242
|
encoding: 'utf-8',
|
|
177
243
|
maxBuffer: 10 * 1024 * 1024,
|
|
244
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
178
245
|
});
|
|
179
246
|
}
|
|
180
247
|
//# sourceMappingURL=validate.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stackbilt/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"description": "Charter CLI — repo-level governance checks",
|
|
5
5
|
"bin": {
|
|
6
6
|
"charter": "./dist/bin.js"
|
|
@@ -37,12 +37,12 @@
|
|
|
37
37
|
"build": "pnpm exec tsc -p tsconfig.json"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@stackbilt/types": "^0.1.
|
|
41
|
-
"@stackbilt/core": "^0.1.
|
|
42
|
-
"@stackbilt/git": "^0.1.
|
|
43
|
-
"@stackbilt/classify": "^0.1.
|
|
44
|
-
"@stackbilt/validate": "^0.1.
|
|
45
|
-
"@stackbilt/drift": "^0.1.
|
|
40
|
+
"@stackbilt/types": "^0.1.9",
|
|
41
|
+
"@stackbilt/core": "^0.1.9",
|
|
42
|
+
"@stackbilt/git": "^0.1.9",
|
|
43
|
+
"@stackbilt/classify": "^0.1.9",
|
|
44
|
+
"@stackbilt/validate": "^0.1.9",
|
|
45
|
+
"@stackbilt/drift": "^0.1.9"
|
|
46
46
|
},
|
|
47
47
|
"license": "Apache-2.0"
|
|
48
48
|
}
|