@stackbilt/cli 0.1.9 → 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 +22 -10
- package/dist/commands/audit.js +47 -0
- package/dist/commands/validate.js +72 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,16 +4,28 @@ 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
|
-
|
|
16
|
-
|
|
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:
|
|
17
29
|
|
|
18
30
|
```bash
|
|
19
31
|
npm install --save-dev @stackbilt/cli
|
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
|