@whitehatd/crag 0.2.5 → 0.2.6
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 +8 -10
- package/package.json +1 -1
- package/src/analyze/ci-extractors.js +7 -7
- package/src/analyze/normalize.js +16 -0
- package/src/governance/yaml-run.js +15 -3
package/README.md
CHANGED
|
@@ -199,21 +199,20 @@ crag version / crag help
|
|
|
199
199
|
The only file you maintain. 20–30 lines. Everything else is universal or generated.
|
|
200
200
|
|
|
201
201
|
```markdown
|
|
202
|
-
# Governance —
|
|
202
|
+
# Governance — example-app
|
|
203
203
|
|
|
204
204
|
## Identity
|
|
205
|
-
- Project:
|
|
206
|
-
- Description:
|
|
205
|
+
- Project: example-app
|
|
206
|
+
- Description: Example project using crag
|
|
207
207
|
|
|
208
208
|
## Gates (run in order, stop on failure)
|
|
209
|
-
### Frontend (path:
|
|
210
|
-
- npx eslint
|
|
211
|
-
- cd
|
|
209
|
+
### Frontend (path: web/)
|
|
210
|
+
- npx eslint web/ --max-warnings 0
|
|
211
|
+
- cd web && npx vite build
|
|
212
212
|
|
|
213
213
|
### Backend
|
|
214
|
-
-
|
|
215
|
-
- cargo
|
|
216
|
-
- cargo test --manifest-path source/decode/Cargo.toml
|
|
214
|
+
- cargo clippy --all-targets -- -D warnings
|
|
215
|
+
- cargo test
|
|
217
216
|
|
|
218
217
|
### Infrastructure
|
|
219
218
|
- docker compose config --quiet
|
|
@@ -223,7 +222,6 @@ The only file you maintain. 20–30 lines. Everything else is universal or gener
|
|
|
223
222
|
- Auto-commit after gates pass
|
|
224
223
|
|
|
225
224
|
## Security
|
|
226
|
-
- Schematic file uploads only (validate server-side)
|
|
227
225
|
- No hardcoded secrets or API keys
|
|
228
226
|
```
|
|
229
227
|
|
package/package.json
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
|
|
22
22
|
const fs = require('fs');
|
|
23
23
|
const path = require('path');
|
|
24
|
-
const { extractRunCommands } = require('../governance/yaml-run');
|
|
24
|
+
const { extractRunCommands, stripYamlQuotes } = require('../governance/yaml-run');
|
|
25
25
|
const { safeRead } = require('./stacks');
|
|
26
26
|
|
|
27
27
|
/**
|
|
@@ -165,7 +165,7 @@ function extractCircleCommands(content) {
|
|
|
165
165
|
const rest = inline[1].trim();
|
|
166
166
|
if (rest && !rest.startsWith('#') && !rest.startsWith('|') && !rest.startsWith('>') &&
|
|
167
167
|
!rest.startsWith('{') && !rest.startsWith('name:') && !rest.startsWith('command:')) {
|
|
168
|
-
commands.push(rest
|
|
168
|
+
commands.push(stripYamlQuotes(rest));
|
|
169
169
|
}
|
|
170
170
|
}
|
|
171
171
|
// Nested: command: ...
|
|
@@ -173,7 +173,7 @@ function extractCircleCommands(content) {
|
|
|
173
173
|
if (cmdMatch) {
|
|
174
174
|
const rest = cmdMatch[1].trim();
|
|
175
175
|
if (rest && !rest.startsWith('|') && !rest.startsWith('>') && !rest.startsWith('#')) {
|
|
176
|
-
commands.push(rest
|
|
176
|
+
commands.push(stripYamlQuotes(rest));
|
|
177
177
|
} else if (rest === '|' || rest === '>-' || rest.startsWith('|') || rest.startsWith('>')) {
|
|
178
178
|
// Block scalar — collect following lines with greater indent
|
|
179
179
|
const baseIndent = (line.match(/^(\s*)/) || ['', ''])[1].length;
|
|
@@ -220,7 +220,7 @@ function extractAzureCommands(content) {
|
|
|
220
220
|
commands.push(inner.trim());
|
|
221
221
|
}
|
|
222
222
|
} else if (rest && !rest.startsWith('#')) {
|
|
223
|
-
commands.push(rest
|
|
223
|
+
commands.push(stripYamlQuotes(rest));
|
|
224
224
|
}
|
|
225
225
|
}
|
|
226
226
|
return commands;
|
|
@@ -287,7 +287,7 @@ function extractYamlListField(content, fields) {
|
|
|
287
287
|
if (innerIndent <= baseIndent) break;
|
|
288
288
|
const listItem = inner.match(/^\s*-\s*(.+)$/);
|
|
289
289
|
if (listItem) {
|
|
290
|
-
commands.push(listItem[1].trim()
|
|
290
|
+
commands.push(stripYamlQuotes(listItem[1].trim()));
|
|
291
291
|
}
|
|
292
292
|
}
|
|
293
293
|
} else if (/^[|>][+-]?\s*$/.test(rest)) {
|
|
@@ -303,11 +303,11 @@ function extractYamlListField(content, fields) {
|
|
|
303
303
|
// Inline list: script: [cmd1, cmd2]
|
|
304
304
|
const inner = rest.slice(1, rest.indexOf(']') === -1 ? rest.length : rest.indexOf(']'));
|
|
305
305
|
for (const item of inner.split(',')) {
|
|
306
|
-
const trimmed = item.trim()
|
|
306
|
+
const trimmed = stripYamlQuotes(item.trim());
|
|
307
307
|
if (trimmed) commands.push(trimmed);
|
|
308
308
|
}
|
|
309
309
|
} else if (!rest.startsWith('#')) {
|
|
310
|
-
commands.push(rest
|
|
310
|
+
commands.push(stripYamlQuotes(rest));
|
|
311
311
|
}
|
|
312
312
|
}
|
|
313
313
|
|
package/src/analyze/normalize.js
CHANGED
|
@@ -94,6 +94,22 @@ function isNoise(cmd) {
|
|
|
94
94
|
// License checkers are typically gates, but their exact invocation is
|
|
95
95
|
// long and project-specific. Keep them.
|
|
96
96
|
|
|
97
|
+
// Dev/maintenance scripts under a `scripts/` directory are one-off tasks,
|
|
98
|
+
// not gates. FastAPI runs its doc, sponsor, people, translation pipelines
|
|
99
|
+
// this way via `uv run ./scripts/*.py`. These are publishing automations,
|
|
100
|
+
// not quality checks.
|
|
101
|
+
if (/^(uv|poetry|pdm|hatch|rye|pipenv)\s+run\s+(\.\/)?scripts\//.test(trimmed)) return true;
|
|
102
|
+
if (/^python3?\s+(\.\/)?scripts\//.test(trimmed)) return true;
|
|
103
|
+
if (/^node\s+(\.\/)?scripts\//.test(trimmed)) return true;
|
|
104
|
+
if (/^(bash|sh)\s+(\.\/)?scripts\//.test(trimmed)) return true;
|
|
105
|
+
if (/^npx\s+(tsx?|ts-node)\s+(\.\/)?scripts\//.test(trimmed)) return true;
|
|
106
|
+
|
|
107
|
+
// Shell control-flow fragments leaked from block scalars. When a `run: |`
|
|
108
|
+
// wraps a multi-line if/for/while/case, our line-based extractor pulls out
|
|
109
|
+
// the control keyword line as a pseudo-command.
|
|
110
|
+
if (/^(if|then|else|elif|fi|for|do|done|while|until|case|esac)\s/.test(trimmed)) return true;
|
|
111
|
+
if (/^(then|else|fi|do|done|esac)$/.test(trimmed)) return true;
|
|
112
|
+
|
|
97
113
|
return false;
|
|
98
114
|
}
|
|
99
115
|
|
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Strip surrounding matching quotes from a YAML scalar value.
|
|
5
|
+
*
|
|
6
|
+
* Only strips when the ENTIRE string is wrapped in matching single or double
|
|
7
|
+
* quotes. Previously we used `replace(/^["']|["']$/g, '')` which stripped a
|
|
8
|
+
* trailing quote even when no leading quote existed — that truncated commands
|
|
9
|
+
* like `make test-X ARGS='--workspace --benches'` to `make test-X ARGS='--workspace --benches`.
|
|
10
|
+
*/
|
|
11
|
+
function stripYamlQuotes(str) {
|
|
12
|
+
const m = str.match(/^(['"])(.*)\1$/);
|
|
13
|
+
return m ? m[2] : str;
|
|
14
|
+
}
|
|
15
|
+
|
|
3
16
|
/**
|
|
4
17
|
* Shared YAML `run:` command extraction for GitHub Actions workflows.
|
|
5
18
|
*
|
|
@@ -42,8 +55,7 @@ function extractRunCommands(content) {
|
|
|
42
55
|
if (trimmed && !trimmed.startsWith('#')) commands.push(trimmed);
|
|
43
56
|
}
|
|
44
57
|
} else if (rest && !rest.startsWith('#')) {
|
|
45
|
-
|
|
46
|
-
commands.push(rest.replace(/^["']|["']$/g, ''));
|
|
58
|
+
commands.push(stripYamlQuotes(rest));
|
|
47
59
|
}
|
|
48
60
|
}
|
|
49
61
|
|
|
@@ -142,4 +154,4 @@ function isGateCommand(cmd) {
|
|
|
142
154
|
return patterns.some((p) => p.test(cmd));
|
|
143
155
|
}
|
|
144
156
|
|
|
145
|
-
module.exports = { extractRunCommands, isGateCommand };
|
|
157
|
+
module.exports = { extractRunCommands, isGateCommand, stripYamlQuotes };
|