@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 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 — StructuAI
202
+ # Governance — example-app
203
203
 
204
204
  ## Identity
205
- - Project: StructuAI
206
- - Description: AI-powered Minecraft schematic describer
205
+ - Project: example-app
206
+ - Description: Example project using crag
207
207
 
208
208
  ## Gates (run in order, stop on failure)
209
- ### Frontend (path: frontend/)
210
- - npx eslint frontend/ --max-warnings 0
211
- - cd frontend && npx vite build
209
+ ### Frontend (path: web/)
210
+ - npx eslint web/ --max-warnings 0
211
+ - cd web && npx vite build
212
212
 
213
213
  ### Backend
214
- - node --check scripts/api-server.js
215
- - cargo clippy --manifest-path source/decode/Cargo.toml
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@whitehatd/crag",
3
- "version": "0.2.5",
3
+ "version": "0.2.6",
4
4
  "description": "The bedrock layer for AI coding agents. One governance.md. Any project. Never stale.",
5
5
  "bin": {
6
6
  "crag": "bin/crag.js"
@@ -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.replace(/^["']|["']$/g, ''));
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.replace(/^["']|["']$/g, ''));
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.replace(/^["']|["']$/g, ''));
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().replace(/^["']|["']$/g, ''));
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().replace(/^["']|["']$/g, '');
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.replace(/^["']|["']$/g, ''));
310
+ commands.push(stripYamlQuotes(rest));
311
311
  }
312
312
  }
313
313
 
@@ -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
- // Inline: strip surrounding single/double quotes if present
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 };