Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 21 additions & 3 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ inputs:
description: Code scanning category for Code-Warden SARIF results.
required: false
default: code-warden
config:
description: Path to a codewarden.json config file in the repo. Overrides built-in defaults.
required: false
default: ''

outputs:
report-path:
Expand Down Expand Up @@ -60,18 +64,32 @@ runs:
id: governance
continue-on-error: true
shell: bash
run: node "${{ github.action_path }}/code-warden/tools/governance-report.js" "${{ inputs.path }}"
run: |
CONFIG_FLAG=""
if [ -n "${{ inputs.config }}" ] && [ -f "${{ inputs.config }}" ]; then
CONFIG_FLAG="--config=${{ inputs.config }}"
fi
node "${{ github.action_path }}/code-warden/tools/governance-report.js" "${{ inputs.path }}" $CONFIG_FLAG

- name: Publish governance summary
if: ${{ always() && inputs.summary == 'true' }}
shell: bash
run: node "${{ github.action_path }}/code-warden/tools/governance-report.js" "${{ inputs.path }}" --format=md >> "$GITHUB_STEP_SUMMARY" || true
run: |
CONFIG_FLAG=""
if [ -n "${{ inputs.config }}" ] && [ -f "${{ inputs.config }}" ]; then
CONFIG_FLAG="--config=${{ inputs.config }}"
fi
node "${{ github.action_path }}/code-warden/tools/governance-report.js" "${{ inputs.path }}" --format=md $CONFIG_FLAG >> "$GITHUB_STEP_SUMMARY" || true

- name: Generate SARIF report
if: ${{ always() && inputs.sarif == 'true' }}
shell: bash
run: |
node "${{ github.action_path }}/code-warden/tools/governance-report.js" "${{ inputs.path }}" --format=sarif > "${{ steps.sarif-path.outputs.path }}" || true
CONFIG_FLAG=""
if [ -n "${{ inputs.config }}" ] && [ -f "${{ inputs.config }}" ]; then
CONFIG_FLAG="--config=${{ inputs.config }}"
fi
node "${{ github.action_path }}/code-warden/tools/governance-report.js" "${{ inputs.path }}" --format=sarif $CONFIG_FLAG > "${{ steps.sarif-path.outputs.path }}" || true
test -s "${{ steps.sarif-path.outputs.path }}"

- name: Upload SARIF report
Expand Down
8 changes: 8 additions & 0 deletions code-warden/CONFIGURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ Located at the root of the skill folder. Default configuration:
},
"safety": {
"exempt_from_blast_radius": ["tests/", "docs/", "scripts/"]
},
"lint": {
"exclude_paths": []
},
"secrets": {
"allowlist": []
}
}
```
Expand All @@ -28,6 +34,8 @@ Located at the root of the skill folder. Default configuration:
| `pre_flight_trigger_lines` | 150 | Forces a JSON manifest before large outputs. |
| `human_checkpoint_files` | 2 | Requires human `[AWAITING CONFIRMATION]` before modifying this many files simultaneously. |
| `exempt_from_blast_radius` | (list) | Skips strict rewriting rollback plans on these file directories. |
| `lint.exclude_paths` | `[]` | Path prefixes excluded from file-length checks. Use for docs, generated files, or vendored code (e.g. `["Documents/", "generated/"]`). |
| `secrets.allowlist` | `[]` | Path prefixes excluded from hardcoded-credential scanning. Use for files with known-safe localhost dev URLs or test fixtures (e.g. `["scripts/indexer.config.toml"]`). |

---

Expand Down
6 changes: 6 additions & 0 deletions code-warden/codewarden.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@
"scripts/"
]
},
"lint": {
"exclude_paths": []
},
"secrets": {
"allowlist": []
},
"reference_selection": {
"rules": [
{
Expand Down
31 changes: 20 additions & 11 deletions code-warden/tools/governance-report.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,12 @@ function parseArgs(argv) {
const args = argv.slice(2);
const formatArg = args.find(a => a.startsWith('--format='));
const outArg = args.find(a => a.startsWith('--out='));
const configArg = args.find(a => a.startsWith('--config='));
const format = formatArg ? formatArg.split('=')[1] : null;
const out = outArg ? outArg.slice('--out='.length) : null;
const configPath = configArg ? configArg.slice('--config='.length) : null;
const scanPath = args.find(a => !a.startsWith('--')) || '.';
return { format, out, scanPath };
return { format, out, scanPath, configPath };
}

// ---------------------------------------------------------------------------
Expand All @@ -49,8 +51,13 @@ function gitInfo() {
// File length + secrets (single pass over all files)
// ---------------------------------------------------------------------------

function runScans(scanPath) {
const { maxFileLength } = loadConfig();
function matchesAnyPrefix(filePath, prefixes) {
const normalized = filePath.replace(/\\/g, '/');
return prefixes.some(p => normalized.startsWith(p) || normalized === p.replace(/\/$/, ''));
}

function runScans(scanPath, configPath) {
const { maxFileLength, lintExcludePaths, secretsAllowlist } = loadConfig(configPath);
const resolved = path.resolve(scanPath);

if (!fs.existsSync(resolved)) {
Expand All @@ -75,13 +82,15 @@ function runScans(scanPath) {

const rel = scanRootIsDirectory ? path.relative(resolved, f) : path.basename(f);

const lineCount = countLines(content);
if (lineCount > maxFileLength) {
lengthViolations.push({ file: rel, lines: lineCount, limit: maxFileLength });
if (!matchesAnyPrefix(rel, lintExcludePaths)) {
const lineCount = countLines(content);
if (lineCount > maxFileLength) {
lengthViolations.push({ file: rel, lines: lineCount, limit: maxFileLength });
}
}

const hit = scanForSecrets(content);
if (hit) {
if (hit && !matchesAnyPrefix(rel, secretsAllowlist)) {
secretViolations.push({ file: rel, pattern: hit.label, line: hit.line, column: hit.column });
}
}
Expand Down Expand Up @@ -212,9 +221,9 @@ function checkRuntimeHooks() {
// Report assembly
// ---------------------------------------------------------------------------

function generateReport(scanPath) {
function generateReport(scanPath, configPath) {
const repo = gitInfo();
const { fileLength, secrets } = runScans(scanPath);
const { fileLength, secrets } = runScans(scanPath, configPath);
const behavioralTests = checkTests();
const installHealth = checkInstallHealth();
const runtimeHooks = checkRuntimeHooks();
Expand Down Expand Up @@ -315,8 +324,8 @@ function writeReport(outPath, content) {
// Main
// ---------------------------------------------------------------------------

const { format, out, scanPath } = parseArgs(process.argv);
const report = generateReport(scanPath);
const { format, out, scanPath, configPath } = parseArgs(process.argv);
const report = generateReport(scanPath, configPath);

if (out) {
writeReport(out, formatReport(report, format));
Expand Down
12 changes: 10 additions & 2 deletions code-warden/tools/lib/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@ const DEFAULT_CONFIG_PATH = path.join(__dirname, '..', '..', 'codewarden.json');
* Falls back to defaults silently if the file is missing or unparseable.
*
* @param {string} [configPath] - Override the config file location
* @returns {{ maxFileLength: number }}
* @returns {{ maxFileLength: number, lintExcludePaths: string[], secretsAllowlist: string[] }}
*/
function loadConfig(configPath) {
const target = configPath || DEFAULT_CONFIG_PATH;
let maxFileLength = 400;
let lintExcludePaths = [];
let secretsAllowlist = [];

try {
const raw = fs.readFileSync(target, 'utf8');
Expand All @@ -39,11 +41,17 @@ function loadConfig(configPath) {
if (typeof configured === 'number' && configured > 0) {
maxFileLength = configured;
}
if (Array.isArray(cfg?.lint?.exclude_paths)) {
lintExcludePaths = cfg.lint.exclude_paths.filter(p => typeof p === 'string');
}
if (Array.isArray(cfg?.secrets?.allowlist)) {
secretsAllowlist = cfg.secrets.allowlist.filter(p => typeof p === 'string');
}
} catch {
// Missing or invalid config — use defaults
}

return { maxFileLength };
return { maxFileLength, lintExcludePaths, secretsAllowlist };
}

module.exports = { loadConfig, DEFAULT_CONFIG_PATH };