azcodr 2.4.0 → 2.5.1
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/.agents/hooks.json +15 -0
- package/.agents/hooks.json.example +15 -0
- package/.agents/lib/agent-guard-command.js +103 -0
- package/.agents/lib/agent-guard-file.js +77 -0
- package/.agents/lib/agent-guard-tdd.js +77 -0
- package/.agents/lib/agent-guard.js +80 -0
- package/.agents/scripts/agent_guard.js +35 -3
- package/.agents/skills/clean-code-refactor/SKILL.md +1 -0
- package/.agents/skills/lets-build/SKILL.md +3 -2
- package/.agents/skills/lets-build/scripts/bootstrap_workspace.sh +159 -0
- package/.agents/skills/relentless-questioner/SKILL.md +1 -1
- package/AGENTS.md +3 -3
- package/CONTRIBUTING.md +10 -4
- package/README.md +10 -6
- package/docs/generated-enforcement-analysis.md +141 -0
- package/docs/rules/clean_code.md +3 -2
- package/docs/rules/test_driven_development.md +8 -5
- package/lib/index.d.ts +1 -1
- package/lib/index.js +1 -1
- package/lib/scaffold.js +1 -1
- package/lib/scaffold.js.map +1 -1
- package/memory.md +16 -0
- package/package.json +2 -2
- package/scripts/validate/toolchain.js +60 -0
- package/scripts/validate.js +12 -2
- package/src/index.ts +1 -1
- package/src/scaffold.ts +1 -1
package/.agents/hooks.json
CHANGED
|
@@ -14,6 +14,21 @@
|
|
|
14
14
|
}
|
|
15
15
|
]
|
|
16
16
|
},
|
|
17
|
+
"architectural-guard": {
|
|
18
|
+
"enabled": false,
|
|
19
|
+
"PreToolUse": [
|
|
20
|
+
{
|
|
21
|
+
"matcher": "run_command",
|
|
22
|
+
"hooks": [
|
|
23
|
+
{
|
|
24
|
+
"type": "command",
|
|
25
|
+
"command": "./.agents/scripts/agent_guard.js",
|
|
26
|
+
"timeout": 15
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
},
|
|
17
32
|
"post-tool-lint": {
|
|
18
33
|
"enabled": false,
|
|
19
34
|
"PostToolUse": [
|
|
@@ -14,6 +14,21 @@
|
|
|
14
14
|
}
|
|
15
15
|
]
|
|
16
16
|
},
|
|
17
|
+
"architectural-guard": {
|
|
18
|
+
"enabled": false,
|
|
19
|
+
"PreToolUse": [
|
|
20
|
+
{
|
|
21
|
+
"matcher": "run_command",
|
|
22
|
+
"hooks": [
|
|
23
|
+
{
|
|
24
|
+
"type": "command",
|
|
25
|
+
"command": "./.agents/scripts/agent_guard.js",
|
|
26
|
+
"timeout": 15
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
},
|
|
17
32
|
"post-tool-lint": {
|
|
18
33
|
"enabled": false,
|
|
19
34
|
"PostToolUse": [
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command safety guard inspecting shell commands against a destructive denylist.
|
|
3
|
+
*/
|
|
4
|
+
export const DESTRUCTIVE_RULES = [
|
|
5
|
+
{
|
|
6
|
+
pattern: /\brm\s+(-[a-z-]*[rf][a-z-]*\s+)+(\/|\/\*|~|~\/\*|\$home|\$\{home\}|\.|\.\.|\.\/|[a-z]:\\|[a-z]:\/)/i,
|
|
7
|
+
reason: 'destructive rm targeting root, home, drive, or the current directory'
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
pattern: /(^|\s)rm\s+(-[a-z-]*\s+)*(\/|~|[a-z]:\\|[a-z]:\/)($|\s)/i,
|
|
11
|
+
reason: 'destructive rm targeting root or home'
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
pattern: /\brm\s+--recursive.*--force\s+(\/|~|\$home|\.)/i,
|
|
15
|
+
reason: 'destructive rm using long-form flags'
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
pattern: /\brm\s+.*--no-preserve-root/i,
|
|
19
|
+
reason: 'rm --no-preserve-root is forbidden under agent execution'
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
pattern: /\b(wipefs\s+-a|shred\s+-u|find\s+.*-delete)\b/i,
|
|
23
|
+
reason: 'irreversible disk or mass-delete operation'
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
pattern: /\bgit\s+push\s+.*(-f|--force|--mirror)\b/i,
|
|
27
|
+
reason: 'force-push or mirror-push can destroy remote history'
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
pattern: /\bgit\s+push\b.*--force-with-lease\b/i,
|
|
31
|
+
reason: 'force-push variant detected'
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
pattern: /\bgit\s+(reset\s+--hard|clean\s+-[a-z]*[fd])/i,
|
|
35
|
+
reason: 'destructive git worktree operation (reset --hard / clean)'
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
pattern: /\bgit\s+checkout\s+--\s+\./i,
|
|
39
|
+
reason: 'git checkout -- . discards all uncommitted work'
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
pattern: /\bgit\s+branch\s+-D\b/i,
|
|
43
|
+
reason: 'force branch delete'
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
pattern: /\b(drop\s+(database|schema|table)|truncate\s+(table\s+)?[a-z_])/i,
|
|
47
|
+
reason: 'destructive SQL DDL'
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
pattern: /\b(delete\s+from|update\s+[a-z_]+\s+set|alter\s+table)\b/i,
|
|
51
|
+
reason: 'destructive SQL DML'
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
pattern: /\b(grant\s+all|revoke\s+all)\b/i,
|
|
55
|
+
reason: 'privilege escalation in SQL'
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
pattern: /\b(mkfs|dd\s+if=|fdisk|parted\s+\/dev)\b/i,
|
|
59
|
+
reason: 'raw disk operation'
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
pattern: /(:[\s]*\([\s]*\)[\s]*\{|:[\s]*\|[\s]*:)/,
|
|
63
|
+
reason: 'fork bomb'
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
pattern: /\b(curl|wget)\s+.*\|\s*(sudo\s+)?(ba)?sh\b/i,
|
|
67
|
+
reason: 'piping download directly into shell execution'
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
pattern: /\b(base64\s+-d|base64\s+--decode)\s+.*\|/i,
|
|
71
|
+
reason: 'decoding encoded payload into a pipe'
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
pattern: /\b(npm|yarn|pnpm)\s+publish\b/i,
|
|
75
|
+
reason: 'package publish must run via release workflow, not ad-hoc tool call'
|
|
76
|
+
}
|
|
77
|
+
];
|
|
78
|
+
export function normalizeCommand(raw) {
|
|
79
|
+
return raw.replace(/[\r\n\t]+/g, ' ').replace(/\s+/g, ' ').trim();
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Checks a command string against the safety denylist.
|
|
83
|
+
*
|
|
84
|
+
* @param command - Raw shell command string.
|
|
85
|
+
* @returns Violation details if blocked, or clean outcome.
|
|
86
|
+
*/
|
|
87
|
+
export function inspectCommand(command) {
|
|
88
|
+
const normalized = normalizeCommand(command);
|
|
89
|
+
if (!normalized)
|
|
90
|
+
return { blocked: false };
|
|
91
|
+
for (const rule of DESTRUCTIVE_RULES) {
|
|
92
|
+
if (rule.pattern.test(normalized)) {
|
|
93
|
+
return {
|
|
94
|
+
blocked: true,
|
|
95
|
+
reason: rule.reason,
|
|
96
|
+
command: normalized.slice(0, 300)
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return { blocked: false, command: normalized };
|
|
101
|
+
}
|
|
102
|
+
export default { DESTRUCTIVE_RULES, normalizeCommand, inspectCommand };
|
|
103
|
+
//# sourceMappingURL=agent-guard-command.js.map
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
export const DEFAULT_MAX_FILE_LINES = 300;
|
|
4
|
+
export function countLines(text) {
|
|
5
|
+
if (!text)
|
|
6
|
+
return 0;
|
|
7
|
+
return text.split('\n').length;
|
|
8
|
+
}
|
|
9
|
+
export function readExistingLines(filePath) {
|
|
10
|
+
if (!fs.existsSync(filePath))
|
|
11
|
+
return 0;
|
|
12
|
+
try {
|
|
13
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
14
|
+
return countLines(content);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return 0;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function calculateProjectedLines(currentLines, input) {
|
|
21
|
+
if (input.incomingContent !== undefined) {
|
|
22
|
+
return countLines(input.incomingContent);
|
|
23
|
+
}
|
|
24
|
+
if (input.replacementContent !== undefined) {
|
|
25
|
+
const addedLines = countLines(input.replacementContent);
|
|
26
|
+
const removedLines = input.targetContent !== undefined ? countLines(input.targetContent) : 1;
|
|
27
|
+
return Math.max(0, currentLines + addedLines - removedLines);
|
|
28
|
+
}
|
|
29
|
+
return currentLines;
|
|
30
|
+
}
|
|
31
|
+
function evaluateLineThresholds(params) {
|
|
32
|
+
const { filePath, currentLines, projectedLines, maxLines } = params;
|
|
33
|
+
const base = { currentLines, projectedLines, maxLines };
|
|
34
|
+
const relPath = path.basename(filePath);
|
|
35
|
+
if (currentLines > maxLines) {
|
|
36
|
+
if (projectedLines >= currentLines) {
|
|
37
|
+
return {
|
|
38
|
+
...base,
|
|
39
|
+
blocked: true,
|
|
40
|
+
reason: `Refactor-Before-Add violation: '${relPath}' currently has ${currentLines} lines (limit: ${maxLines}). Adding or maintaining lines in an overflowing file is blocked. Refactor into modular units under green tests first.`
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return { ...base, blocked: false };
|
|
44
|
+
}
|
|
45
|
+
if (projectedLines > maxLines) {
|
|
46
|
+
return {
|
|
47
|
+
...base,
|
|
48
|
+
blocked: true,
|
|
49
|
+
reason: `File line budget exceeded: '${relPath}' would grow to ${projectedLines} lines (limit: ${maxLines}). Decompose into modular components before appending more code.`
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return { ...base, blocked: false };
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Checks a proposed file write or edit against Refactor-Before-Add rules.
|
|
56
|
+
*
|
|
57
|
+
* @param input - File path and proposed content.
|
|
58
|
+
* @returns Decision result indicating whether the write is blocked.
|
|
59
|
+
*/
|
|
60
|
+
export function inspectFileWrite(input) {
|
|
61
|
+
const maxLines = input.maxLines ?? DEFAULT_MAX_FILE_LINES;
|
|
62
|
+
const currentLines = readExistingLines(input.filePath);
|
|
63
|
+
const projectedLines = calculateProjectedLines(currentLines, input);
|
|
64
|
+
return evaluateLineThresholds({
|
|
65
|
+
filePath: input.filePath,
|
|
66
|
+
currentLines,
|
|
67
|
+
projectedLines,
|
|
68
|
+
maxLines
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
export default {
|
|
72
|
+
DEFAULT_MAX_FILE_LINES,
|
|
73
|
+
countLines,
|
|
74
|
+
readExistingLines,
|
|
75
|
+
inspectFileWrite
|
|
76
|
+
};
|
|
77
|
+
//# sourceMappingURL=agent-guard-file.js.map
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
export function isTestFile(filePath) {
|
|
4
|
+
const norm = filePath.replace(/\\/g, '/').toLowerCase();
|
|
5
|
+
if (/(^|\/)(tests?|__tests__)(\/|$)/i.test(norm)) {
|
|
6
|
+
return true;
|
|
7
|
+
}
|
|
8
|
+
return /\.(test|spec)\.[a-z0-9]+$/i.test(norm);
|
|
9
|
+
}
|
|
10
|
+
export function isNonCodeFile(filePath) {
|
|
11
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
12
|
+
const nonCodeExts = new Set(['.md', '.json', '.yml', '.yaml', '.txt', '.toml', '.lock', '.svg', '.png']);
|
|
13
|
+
return nonCodeExts.has(ext);
|
|
14
|
+
}
|
|
15
|
+
export function isProductionFile(filePath) {
|
|
16
|
+
if (isNonCodeFile(filePath))
|
|
17
|
+
return false;
|
|
18
|
+
if (isTestFile(filePath))
|
|
19
|
+
return false;
|
|
20
|
+
const norm = filePath.replace(/\\/g, '/').toLowerCase();
|
|
21
|
+
return norm.includes('src/') || norm.includes('lib/') || norm.includes('app/');
|
|
22
|
+
}
|
|
23
|
+
export function readTddState(stateFile) {
|
|
24
|
+
if (!fs.existsSync(stateFile)) {
|
|
25
|
+
return { lastFailingTestRecorded: false };
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
const raw = fs.readFileSync(stateFile, 'utf-8');
|
|
29
|
+
return JSON.parse(raw);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return { lastFailingTestRecorded: false };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export function writeTddState(stateFile, state) {
|
|
36
|
+
try {
|
|
37
|
+
const dir = path.dirname(stateFile);
|
|
38
|
+
if (!fs.existsSync(dir))
|
|
39
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
40
|
+
fs.writeFileSync(stateFile, JSON.stringify(state, null, 2), 'utf-8');
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// Fail-safe write: do not crash agent if state cannot be persisted
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Checks whether an edit to a file is allowed under RED-before-GREEN TDD rules.
|
|
48
|
+
*
|
|
49
|
+
* @param filePath - Path to file being edited.
|
|
50
|
+
* @param state - Current session TDD state.
|
|
51
|
+
* @param enforceTestFirst - Whether test-first enforcement is strictly enabled.
|
|
52
|
+
* @returns Decision result indicating whether edit is blocked.
|
|
53
|
+
*/
|
|
54
|
+
export function inspectTddRequirement(filePath, state, enforceTestFirst = false) {
|
|
55
|
+
const prod = isProductionFile(filePath);
|
|
56
|
+
if (!enforceTestFirst || !prod) {
|
|
57
|
+
return { blocked: false, isProductionFile: prod };
|
|
58
|
+
}
|
|
59
|
+
if (!state.lastFailingTestRecorded) {
|
|
60
|
+
const baseName = path.basename(filePath);
|
|
61
|
+
return {
|
|
62
|
+
blocked: true,
|
|
63
|
+
isProductionFile: true,
|
|
64
|
+
reason: `Test-First (RED-before-GREEN) requirement: Production file '${baseName}' cannot be edited before a failing test (RED) is recorded. Author a failing unit or acceptance test first, verify failure output, then edit production code.`
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
return { blocked: false, isProductionFile: true };
|
|
68
|
+
}
|
|
69
|
+
export default {
|
|
70
|
+
isTestFile,
|
|
71
|
+
isNonCodeFile,
|
|
72
|
+
isProductionFile,
|
|
73
|
+
readTddState,
|
|
74
|
+
writeTddState,
|
|
75
|
+
inspectTddRequirement
|
|
76
|
+
};
|
|
77
|
+
//# sourceMappingURL=agent-guard-tdd.js.map
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { inspectCommand } from './agent-guard-command.js';
|
|
3
|
+
import { inspectFileWrite } from './agent-guard-file.js';
|
|
4
|
+
import { inspectTddRequirement, readTddState, writeTddState } from './agent-guard-tdd.js';
|
|
5
|
+
function parseEnvelope(raw) {
|
|
6
|
+
try {
|
|
7
|
+
const trimmed = raw.trim();
|
|
8
|
+
if (!trimmed.startsWith('{'))
|
|
9
|
+
return null;
|
|
10
|
+
return JSON.parse(trimmed);
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function extractKey(input, keys) {
|
|
17
|
+
for (const k of keys) {
|
|
18
|
+
if (typeof input[k] === 'string')
|
|
19
|
+
return input[k];
|
|
20
|
+
}
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
function extractFilePath(input) {
|
|
24
|
+
return extractKey(input, ['TargetFile', 'file_path', 'path', 'filePath']);
|
|
25
|
+
}
|
|
26
|
+
function extractCommand(input) {
|
|
27
|
+
return extractKey(input, ['CommandLine', 'command', 'cmd']);
|
|
28
|
+
}
|
|
29
|
+
function inspectPreCommand(cmd) {
|
|
30
|
+
const violation = inspectCommand(cmd);
|
|
31
|
+
if (violation.blocked) {
|
|
32
|
+
return { allowed: false, reason: violation.reason, source: 'command' };
|
|
33
|
+
}
|
|
34
|
+
return { allowed: true };
|
|
35
|
+
}
|
|
36
|
+
function inspectPreFile(filePath, input, options) {
|
|
37
|
+
const incoming = extractKey(input, ['CodeContent', 'content']);
|
|
38
|
+
const replacement = extractKey(input, ['ReplacementContent', 'replacement']);
|
|
39
|
+
const target = extractKey(input, ['TargetContent', 'target']);
|
|
40
|
+
const fileDecision = inspectFileWrite({
|
|
41
|
+
filePath,
|
|
42
|
+
incomingContent: incoming,
|
|
43
|
+
replacementContent: replacement,
|
|
44
|
+
targetContent: target,
|
|
45
|
+
maxLines: options.maxFileLines
|
|
46
|
+
});
|
|
47
|
+
if (fileDecision.blocked) {
|
|
48
|
+
return { allowed: false, reason: fileDecision.reason, source: 'file' };
|
|
49
|
+
}
|
|
50
|
+
const root = options.workspaceRoot ?? process.cwd();
|
|
51
|
+
const statePath = path.join(root, '.agents', '.session-state.json');
|
|
52
|
+
const state = readTddState(statePath);
|
|
53
|
+
const tddDecision = inspectTddRequirement(filePath, state, options.enforceTestFirst);
|
|
54
|
+
if (tddDecision.blocked) {
|
|
55
|
+
return { allowed: false, reason: tddDecision.reason, source: 'tdd' };
|
|
56
|
+
}
|
|
57
|
+
return { allowed: true };
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Inspects a tool-use request before execution (PreToolUse hook).
|
|
61
|
+
*
|
|
62
|
+
* @param rawInput - Raw command string, JSON envelope, or argv slice.
|
|
63
|
+
* @param options - Guard configuration options.
|
|
64
|
+
* @returns Guard decision indicating whether tool execution is permitted.
|
|
65
|
+
*/
|
|
66
|
+
export function inspectPreTool(rawInput, options = {}) {
|
|
67
|
+
const env = parseEnvelope(rawInput);
|
|
68
|
+
if (env && env.tool_input) {
|
|
69
|
+
const cmd = extractCommand(env.tool_input);
|
|
70
|
+
if (cmd)
|
|
71
|
+
return inspectPreCommand(cmd);
|
|
72
|
+
const targetFile = extractFilePath(env.tool_input);
|
|
73
|
+
if (targetFile)
|
|
74
|
+
return inspectPreFile(targetFile, env.tool_input, options);
|
|
75
|
+
}
|
|
76
|
+
return inspectPreCommand(rawInput);
|
|
77
|
+
}
|
|
78
|
+
export { inspectCommand, inspectFileWrite, inspectTddRequirement, readTddState, writeTddState };
|
|
79
|
+
export default { inspectPreTool, inspectCommand, inspectFileWrite, inspectTddRequirement };
|
|
80
|
+
//# sourceMappingURL=agent-guard.js.map
|
|
@@ -14,10 +14,39 @@ import process from 'node:process';
|
|
|
14
14
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
15
15
|
|
|
16
16
|
const selfDir = path.dirname(fileURLToPath(import.meta.url));
|
|
17
|
-
|
|
17
|
+
|
|
18
|
+
// Resolution order (first existing file wins):
|
|
19
|
+
// 1. AZCODR_GUARD_ENGINE override (tests, embedders).
|
|
20
|
+
// 2. ../lib/agent-guard.js — vendored engine shipped inside `.agents/`
|
|
21
|
+
// (present in scaffolded projects; `.agents` is copied recursively).
|
|
22
|
+
// 3. ../../lib/agent-guard.js — azcodr repo dev layout (fallback only).
|
|
23
|
+
const guardCandidates = [
|
|
24
|
+
process.env.AZCODR_GUARD_ENGINE,
|
|
25
|
+
path.resolve(selfDir, '../lib/agent-guard.js'),
|
|
26
|
+
path.resolve(selfDir, '../../lib/agent-guard.js')
|
|
27
|
+
].filter(Boolean);
|
|
18
28
|
|
|
19
29
|
async function getGuardModule() {
|
|
20
|
-
|
|
30
|
+
const tried = [];
|
|
31
|
+
for (const candidate of guardCandidates) {
|
|
32
|
+
if (!fs.existsSync(candidate)) {
|
|
33
|
+
tried.push(candidate);
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
return await import(pathToFileURL(candidate).href);
|
|
38
|
+
} catch (err) {
|
|
39
|
+
tried.push(`${candidate} (unreadable: ${err.message})`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// Fail CLOSED: a guard with no engine must be loud, never a silent allow.
|
|
43
|
+
// (Unparseable tool payloads still fail open inside inspectPreTool per
|
|
44
|
+
// ADR-005; a missing engine is a broken install, not an ambiguous input.)
|
|
45
|
+
console.error(
|
|
46
|
+
'🚨 Architectural Guard misconfigured: engine module not found, refusing to fail open. Tried:\n' +
|
|
47
|
+
tried.map((t) => ` - ${t}`).join('\n')
|
|
48
|
+
);
|
|
49
|
+
process.exit(2);
|
|
21
50
|
}
|
|
22
51
|
|
|
23
52
|
async function readStdin(timeoutMs = 1500) {
|
|
@@ -60,4 +89,7 @@ async function main() {
|
|
|
60
89
|
process.exit(0);
|
|
61
90
|
}
|
|
62
91
|
|
|
63
|
-
main().catch(() =>
|
|
92
|
+
main().catch((err) => {
|
|
93
|
+
console.error(`🚨 Architectural Guard crashed: ${err?.message || err}`);
|
|
94
|
+
process.exit(2);
|
|
95
|
+
});
|
|
@@ -51,6 +51,7 @@ Target concrete flaws:
|
|
|
51
51
|
### Step 5: Verify Continuous Green State
|
|
52
52
|
- Run tests after every single atomic change. Use the workspace's own command — do not assume one exists. Verify with the package manifest first (`node -p "JSON.stringify(require('./package.json').scripts)"`); for this template that is `npm test`, with `npm run test:coverage` for the coverage gate.
|
|
53
53
|
- Ensure coverage remains at **100.00%**.
|
|
54
|
+
- Verify architectural boundaries: Run `npx azcodr boundaries` to guarantee the refactoring introduced 0 circular dependencies and 0 layer boundary breaches.
|
|
54
55
|
|
|
55
56
|
---
|
|
56
57
|
|
|
@@ -100,7 +100,7 @@ Upon user confirmation:
|
|
|
100
100
|
- *Extension:* `manifest.json`, `src/background/index.ts`, `src/content/index.ts`, `src/popup/index.html`.
|
|
101
101
|
- *Game / Engine:* `src/core/`, `src/ecs/`, asset manifest, frame loop entrypoint.
|
|
102
102
|
- *CLI:* `src/cmd/`, `src/core/`, CLI entrypoint with exit code handling.
|
|
103
|
-
3. Generate build manifests (`Cargo.toml`, `package.json`, `go.mod`, `pyproject.toml`),
|
|
103
|
+
3. Generate build manifests (`Cargo.toml`, `package.json`, `go.mod`, `pyproject.toml`) for the recorded language, install the pinned linter from the emitted gate configuration (`eslint.config.js`, `ruff.toml`, `clippy.toml`, `.golangci.yml`, `checkstyle.xml`, `.editorconfig` CA block, `.clang-tidy` — written deterministically by `bootstrap_workspace.sh`, never invented). Node profiles arrive with `package.json` lint already rewired to `eslint .`; other profiles need the lint entry wired by hand. Prove the gate in Phase 5, and keep the boundary smoke test (`scripts/smoke_test.sh`).
|
|
104
104
|
4. **Replace Starter README with Project-Specific README**:
|
|
105
105
|
Generate a clean, project-specific `README.md` using [references/project_readme_template.md](./references/project_readme_template.md), completely replacing meta-template content with the project's actual name, mission, stack highlights, quickstart commands, and directory tree.
|
|
106
106
|
|
|
@@ -114,6 +114,7 @@ Upon user confirmation:
|
|
|
114
114
|
2. Execute toolchain dependency checks, build commands, and health/smoke tests:
|
|
115
115
|
- Compile code and verify zero compiler or lint errors.
|
|
116
116
|
- Verify boundary verification smoke test (`scripts/smoke_test.sh`).
|
|
117
|
+
- **Prove enforcement is live:** enable the shipped hooks in `.agents/hooks.json` (`safety-guard`, `architectural-guard`), then deliberately attempt a blocked action (e.g. append lines to a file already over budget, or a denied command) and confirm the hook rejects it before re-disabling nothing — hooks stay enabled. A guard that has never blocked anything is a rumor, not a control.
|
|
117
118
|
3. **Mandatory Handover to Domain Analysis (STOP & PIVOT):**
|
|
118
119
|
- **`lets-build` IS NOW COMPLETE.** Do NOT proceed to write domain business entities, repositories, or application features.
|
|
119
120
|
- Present the bootstrapped technical skeleton to the user.
|
|
@@ -160,7 +161,7 @@ Upon user confirmation:
|
|
|
160
161
|
|
|
161
162
|
## 4. Quality & Verification
|
|
162
163
|
- **Testing Strategy:** Outside-In TDD with Nano-Cycles (Uncle Bob's 3 Laws)
|
|
163
|
-
- **Code Health Gates:** 100
|
|
164
|
+
- **Code Health Gates:** 100% coverage on all metrics + mutation kill gate on guards, zero lint errors
|
|
164
165
|
- **DevSecOps:** <Semgrep / Trivy / Gitleaks / None>
|
|
165
166
|
```
|
|
166
167
|
|
|
@@ -324,6 +324,165 @@ EOF
|
|
|
324
324
|
;;
|
|
325
325
|
esac
|
|
326
326
|
|
|
327
|
+
# ------------------------------------------------------------------------------
|
|
328
|
+
# DETERMINISTIC TOOLCHAIN FILES. Directories alone enforce nothing: until this
|
|
329
|
+
# section existed, every language produced zero gate configurations and the
|
|
330
|
+
# starter `lint` script was an echo placeholder, so all enforcement was
|
|
331
|
+
# agent-authored. Each profile below emits its pinned gate configuration with
|
|
332
|
+
# values mirroring docs/rules/clean_code.md (300/30/10/3); the agent installs
|
|
333
|
+
# the named tool and proves the gate in Phase 5. Files are created only when
|
|
334
|
+
# absent, so re-runs never clobber agent-authored configs. Build manifests
|
|
335
|
+
# (Cargo.toml, go.mod, csproj, ...) stay with the agent: they carry project
|
|
336
|
+
# naming and version decisions no script may invent.
|
|
337
|
+
# ------------------------------------------------------------------------------
|
|
338
|
+
write_unless_exists() {
|
|
339
|
+
local target="$1"
|
|
340
|
+
if [[ -f "${target}" ]]; then
|
|
341
|
+
echo " keeping existing ${target}"
|
|
342
|
+
return
|
|
343
|
+
fi
|
|
344
|
+
mkdir -p "$(dirname "${target}")"
|
|
345
|
+
cat > "${target}"
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
append_unless_present() {
|
|
349
|
+
local target="$1"
|
|
350
|
+
local marker="$2"
|
|
351
|
+
if [[ -f "${target}" ]] && grep -qF "${marker}" "${target}"; then
|
|
352
|
+
echo " keeping existing ${target} gates"
|
|
353
|
+
return
|
|
354
|
+
fi
|
|
355
|
+
cat >> "${target}"
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
echo "6. Emitting deterministic toolchain configs for '${LANGUAGE}'..."
|
|
359
|
+
case "${LANGUAGE}" in
|
|
360
|
+
typescript|javascript|deno|bun)
|
|
361
|
+
write_unless_exists "${WORKSPACE_ROOT}/eslint.config.js" << 'EOF'
|
|
362
|
+
// Deterministic fitness functions (azcodr clean_code.md section 5).
|
|
363
|
+
// Install the pinned tool, then prove the gate: npm run lint
|
|
364
|
+
export default [
|
|
365
|
+
{
|
|
366
|
+
files: ['src/**/*.{js,ts}', 'tests/**/*.{js,ts}'],
|
|
367
|
+
rules: {
|
|
368
|
+
'max-lines': ['error', 300],
|
|
369
|
+
'max-lines-per-function': ['error', 30],
|
|
370
|
+
complexity: ['error', 10],
|
|
371
|
+
'max-params': ['error', 3]
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
];
|
|
375
|
+
EOF
|
|
376
|
+
;;
|
|
377
|
+
python)
|
|
378
|
+
write_unless_exists "${WORKSPACE_ROOT}/ruff.toml" << 'EOF'
|
|
379
|
+
# Deterministic fitness functions (azcodr clean_code.md section 5).
|
|
380
|
+
# Install the pinned tool, then prove the gate: ruff check .
|
|
381
|
+
[lint]
|
|
382
|
+
select = ["E", "F", "C901", "PLR0912", "PLR0913", "PLR0915"]
|
|
383
|
+
[lint.mccabe]
|
|
384
|
+
max-complexity = 10
|
|
385
|
+
[lint.pylint]
|
|
386
|
+
max-args = 3
|
|
387
|
+
max-statements = 30
|
|
388
|
+
# NOTE: ruff has no file-length rule; the 300-line file cap is enforced by
|
|
389
|
+
# the project's lint entry (see lets-build Phase 5 proof).
|
|
390
|
+
EOF
|
|
391
|
+
;;
|
|
392
|
+
rust)
|
|
393
|
+
write_unless_exists "${WORKSPACE_ROOT}/clippy.toml" << 'EOF'
|
|
394
|
+
# Deterministic fitness functions (azcodr clean_code.md section 5).
|
|
395
|
+
# Enforce with: cargo clippy -- -D clippy::too_many_lines -D clippy::cognitive_complexity -D clippy::too_many_arguments
|
|
396
|
+
too-many-lines-threshold = 30
|
|
397
|
+
cognitive-complexity-threshold = 10
|
|
398
|
+
too-many-arguments-threshold = 3
|
|
399
|
+
# NOTE: clippy has no file-length lint; the 300-line file cap is enforced by
|
|
400
|
+
# the project's lint entry (see lets-build Phase 5 proof).
|
|
401
|
+
EOF
|
|
402
|
+
;;
|
|
403
|
+
go)
|
|
404
|
+
write_unless_exists "${WORKSPACE_ROOT}/.golangci.yml" << 'EOF'
|
|
405
|
+
# Deterministic fitness functions (azcodr clean_code.md section 5).
|
|
406
|
+
# Install the pinned tool, then prove the gate: golangci-lint run ./...
|
|
407
|
+
linters:
|
|
408
|
+
enable: [funlen, gocyclo]
|
|
409
|
+
linters-settings:
|
|
410
|
+
funlen:
|
|
411
|
+
lines: 30
|
|
412
|
+
statements: 25
|
|
413
|
+
gocyclo:
|
|
414
|
+
min-complexity: 10
|
|
415
|
+
# NOTE: no golangci-native file-length check; the 300-line file cap is
|
|
416
|
+
# enforced by the project's lint entry (see lets-build Phase 5 proof).
|
|
417
|
+
EOF
|
|
418
|
+
;;
|
|
419
|
+
java)
|
|
420
|
+
write_unless_exists "${WORKSPACE_ROOT}/checkstyle.xml" << 'EOF'
|
|
421
|
+
<?xml version="1.0"?>
|
|
422
|
+
<!-- Deterministic fitness functions (azcodr clean_code.md section 5). -->
|
|
423
|
+
<!DOCTYPE module PUBLIC "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN" "https://checkstyle.org/dtds/configuration_1_3.dtd">
|
|
424
|
+
<module name="Checker">
|
|
425
|
+
<module name="FileLength">
|
|
426
|
+
<property name="max" value="300"/>
|
|
427
|
+
</module>
|
|
428
|
+
<module name="TreeWalker">
|
|
429
|
+
<module name="MethodLength">
|
|
430
|
+
<property name="max" value="30"/>
|
|
431
|
+
</module>
|
|
432
|
+
<module name="CyclomaticComplexity">
|
|
433
|
+
<property name="max" value="10"/>
|
|
434
|
+
</module>
|
|
435
|
+
<module name="ParameterNumber">
|
|
436
|
+
<property name="max" value="3"/>
|
|
437
|
+
</module>
|
|
438
|
+
</module>
|
|
439
|
+
</module>
|
|
440
|
+
EOF
|
|
441
|
+
;;
|
|
442
|
+
csharp)
|
|
443
|
+
append_unless_present "${WORKSPACE_ROOT}/.editorconfig" "azcodr fitness functions" << 'EOF'
|
|
444
|
+
|
|
445
|
+
# --- azcodr fitness functions (CA gates; exact numbers in lint entry) ---
|
|
446
|
+
[*.cs]
|
|
447
|
+
dotnet_diagnostic.CA1501.severity = error
|
|
448
|
+
dotnet_diagnostic.CA1502.severity = error
|
|
449
|
+
EOF
|
|
450
|
+
;;
|
|
451
|
+
cpp|c)
|
|
452
|
+
write_unless_exists "${WORKSPACE_ROOT}/.clang-tidy" << 'EOF'
|
|
453
|
+
# Deterministic fitness functions (azcodr clean_code.md section 5).
|
|
454
|
+
Checks: 'readability-function-size,readability-function-cognitive-complexity'
|
|
455
|
+
CheckOptions:
|
|
456
|
+
- { key: readability-function-size.LineThreshold, value: 30 }
|
|
457
|
+
- { key: readability-function-size.ParameterThreshold, value: 3 }
|
|
458
|
+
- { key: readability-function-cognitive-complexity.Threshold, value: 10 }
|
|
459
|
+
# NOTE: clang-tidy has no file-length check; the 300-line file cap is
|
|
460
|
+
# enforced by the project's lint entry (see lets-build Phase 5 proof).
|
|
461
|
+
EOF
|
|
462
|
+
;;
|
|
463
|
+
generic)
|
|
464
|
+
echo " language undecided: no toolchain configs emitted (re-run with a language profile)"
|
|
465
|
+
;;
|
|
466
|
+
esac
|
|
467
|
+
|
|
468
|
+
# For Node profiles the starter package.json ships a lint placeholder that
|
|
469
|
+
# always succeeds. Point it at the emitted config so `npm run lint` fails
|
|
470
|
+
# until the pinned eslint is installed (fail-closed) instead of echoing.
|
|
471
|
+
# Only the placeholder is ever replaced; agent-wired entries are sacred.
|
|
472
|
+
case "${LANGUAGE}" in
|
|
473
|
+
typescript|javascript|deno|bun)
|
|
474
|
+
STARTER_PKG="${WORKSPACE_ROOT}/package.json"
|
|
475
|
+
if [[ -f "${STARTER_PKG}" ]] && grep -q 'No linter configured yet' "${STARTER_PKG}"; then
|
|
476
|
+
if command -v node >/dev/null 2>&1; then
|
|
477
|
+
node -e 'const fs=require("fs");const p=process.argv[1];const j=JSON.parse(fs.readFileSync(p,"utf8"));j.scripts=j.scripts||{};j.scripts.lint="eslint .";fs.writeFileSync(p,JSON.stringify(j,null,2)+"\n");' "${STARTER_PKG}"
|
|
478
|
+
echo " wired package.json lint -> eslint ."
|
|
479
|
+
else
|
|
480
|
+
echo " node not found: leaving starter lint placeholder (agent wires it in Phase 4)"
|
|
481
|
+
fi
|
|
482
|
+
fi
|
|
483
|
+
;;
|
|
484
|
+
esac
|
|
485
|
+
|
|
327
486
|
# ------------------------------------------------------------------------------
|
|
328
487
|
# memory.md is an APPEND-ONLY LEDGER. It is never rewritten automatically.
|
|
329
488
|
#
|
|
@@ -119,7 +119,7 @@ Synthesize the answers into an unambiguous **Feature Alignment Specification (FA
|
|
|
119
119
|
When [Action]
|
|
120
120
|
Then [Observable Outcome]
|
|
121
121
|
```
|
|
122
|
-
- **Test Strategy:** [Contract / Integration / Unit tests required
|
|
122
|
+
- **Test Strategy:** [Contract / Integration / Unit tests required; 100% on all metrics, assertion-free tests rejected]
|
|
123
123
|
```
|
|
124
124
|
|
|
125
125
|
---
|
package/AGENTS.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# AGENTS.md
|
|
2
2
|
|
|
3
|
-
> **azcodr:
|
|
3
|
+
> **azcodr: Architecture governance toolkit, with an optional starter**
|
|
4
4
|
> **Workspace Mission:** Problem-first, topology-aligned production architectures governed by strict systemic atomicity, 100% open-source standards, true incremental TDD nano-cycles, and zero speculative bloat.
|
|
5
5
|
> **Runtime & Tools:** Node.js (`>=22.8.0`), npm (`>=10.0.0`) | `npm test` (test runner), `npm run test:coverage` (coverage gate), `npm run lint`, `npm run validate`.
|
|
6
6
|
> **Node floor rationale:** `>=22.8.0` satisfies Jest native ESM requirements (`--experimental-vm-modules`) and modern LTS security baselines. Node 18 (EOL 2025-04-30) and 20 (EOL 2026-04-30) no longer receive security patches.
|
|
@@ -48,7 +48,7 @@ Progress all tasks systematically through the unified **Agent Cognitive & Agile
|
|
|
48
48
|
2. INTERROGATE / DOMAIN ──► Relentless questioning; Ubiquitous Language, Aggregate invariants & state machines.
|
|
49
49
|
3. PLAN / OUTER TDD ──► Minimal blast radius; failing Outer Acceptance Test (UI/API RED).
|
|
50
50
|
4. EXECUTE / INNER TDD ──► Incremental nano-cycles (Uncle Bob's 3 Laws: 1 micro-assertion RED ➔ MINIMAL pass GREEN ➔ REFACTOR).
|
|
51
|
-
5. VERIFY / DoD & PROOF ──► Outer test turns GREEN; boundary smoke tests & 100
|
|
51
|
+
5. VERIFY / DoD & PROOF ──► Outer test turns GREEN; boundary smoke tests & 100% coverage on all metrics + mutation gate.
|
|
52
52
|
```
|
|
53
53
|
---
|
|
54
54
|
|
|
@@ -58,7 +58,7 @@ To prevent context bloat and keep prompt overhead minimal, detailed engineering
|
|
|
58
58
|
|
|
59
59
|
| Domain | Rule Reference File | When to Consult |
|
|
60
60
|
|---|---|---|
|
|
61
|
-
| **TDD & Isolation** | [docs/rules/test_driven_development.md](./docs/rules/test_driven_development.md) | Outside-In TDD, Uncle Bob's 3 Laws, 100% coverage, test isolation & DB rollback. |
|
|
61
|
+
| **TDD & Isolation** | [docs/rules/test_driven_development.md](./docs/rules/test_driven_development.md) | Outside-In TDD, Uncle Bob's 3 Laws, 100% coverage on all metrics + mutation gate, test isolation & DB rollback. |
|
|
62
62
|
| **Clean Code** | [docs/rules/clean_code.md](./docs/rules/clean_code.md) | Naming, small functions, CQS, SLAP, DRY, DbC, zero side-effects. |
|
|
63
63
|
| **Design Patterns** | [docs/rules/design_patterns.md](./docs/rules/design_patterns.md) | Adapter, Factory, Strategy, Result `<T, E>`, and GoF pattern catalog. |
|
|
64
64
|
| **Type Safety** | [docs/rules/type_safety.md](./docs/rules/type_safety.md) | Compiler strictness, branded nominal types, type discriminators across polyglot languages. |
|
package/CONTRIBUTING.md
CHANGED
|
@@ -49,15 +49,21 @@ Before submitting any code changes, all 5 quality gates must pass with zero erro
|
|
|
49
49
|
npm test
|
|
50
50
|
npm run test:coverage
|
|
51
51
|
```
|
|
52
|
-
*Coverage gates enforced:*
|
|
52
|
+
*Coverage gates enforced (CI fails below 100% on any metric):*
|
|
53
53
|
- **100% lines coverage**
|
|
54
|
-
- **
|
|
55
|
-
- **
|
|
54
|
+
- **100% functions coverage**
|
|
55
|
+
- **100% branches coverage**
|
|
56
|
+
- **100% statements coverage**
|
|
57
|
+
- Plus **100% mutant-kill on guards** (`npm run test:mutation`) and rejection of assertion-free tests.
|
|
56
58
|
5. **Agentic Architecture Validation:**
|
|
57
59
|
```bash
|
|
58
60
|
npm run validate
|
|
59
61
|
```
|
|
60
|
-
Validates root harness parity, progressive disclosure rules, skill formats, markdown links,
|
|
62
|
+
Validates root harness parity, progressive disclosure rules, skill formats, markdown links, ADR ledger consistency, and toolchain gate presence.
|
|
63
|
+
6. **Vendored Guard Sync:** after any change under `src/agent-guard*.ts` and `npm run build`, re-copy the four compiled engine files into `.agents/lib/` (byte-equality is enforced by `tests/agent-guard.test.ts`):
|
|
64
|
+
```bash
|
|
65
|
+
node -e "const fs=require('fs');for(const f of['agent-guard.js','agent-guard-command.js','agent-guard-file.js','agent-guard-tdd.js'])fs.copyFileSync('lib/'+f,'.agents/lib/'+f)"
|
|
66
|
+
```
|
|
61
67
|
|
|
62
68
|
---
|
|
63
69
|
|
package/README.md
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
# azcodr:
|
|
1
|
+
# azcodr: Architecture governance toolkit, with an optional starter
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/azcodr)
|
|
4
4
|
[](https://jsr.io/@azcodr/azcodr)
|
|
5
5
|
[](https://github.com/prosubodh/azcodr/actions/workflows/ci.yml)
|
|
6
|
-
[](https://github.com/prosubodh/azcodr)
|
|
6
|
+
[](https://github.com/prosubodh/azcodr/actions/workflows/ci.yml)
|
|
7
|
+
[](https://github.com/prosubodh/azcodr/blob/main/benchmark/RESULTS.md)
|
|
8
|
+
[](https://www.npmjs.com/package/azcodr)
|
|
9
|
+
[](./LICENSE)
|
|
10
|
+
|
|
11
|
+
> CI-enforced 100% gate (lines / branches / functions / statements) + 100% mutant kill on guards; the public report is the `coverage-report` artifact on every CI run.
|
|
8
12
|
[](https://www.npmjs.com/package/azcodr)
|
|
9
13
|
[](./LICENSE)
|
|
10
14
|
|
|
@@ -127,7 +131,7 @@ The architecture enforces 28 cohesive, single-responsibility domain rules. Read
|
|
|
127
131
|
|
|
128
132
|
| Domain | Rule Reference File | Key Focus & Invariants |
|
|
129
133
|
|---|---|---|
|
|
130
|
-
| **TDD & Isolation** | [`test_driven_development.md`](./docs/rules/test_driven_development.md) | Outside-In TDD, Uncle Bob's 3 Laws, 100% coverage, test isolation & DB rollback. |
|
|
134
|
+
| **TDD & Isolation** | [`test_driven_development.md`](./docs/rules/test_driven_development.md) | Outside-In TDD, Uncle Bob's 3 Laws, 100% coverage on all metrics + mutation gate, test isolation & DB rollback. |
|
|
131
135
|
| **Clean Code** | [`clean_code.md`](./docs/rules/clean_code.md) | Naming, small functions, CQS, SLAP, DRY, DbC, zero side-effects. |
|
|
132
136
|
| **Design Patterns** | [`design_patterns.md`](./docs/rules/design_patterns.md) | Adapter, Factory, Strategy, Result `<T, E>`, and GoF pattern catalog. |
|
|
133
137
|
| **Type Safety** | [`type_safety.md`](./docs/rules/type_safety.md) | Compiler strictness, branded nominal types, type discriminators across polyglot languages. |
|
|
@@ -171,10 +175,10 @@ The architecture enforces 28 cohesive, single-responsibility domain rules. Read
|
|
|
171
175
|
|
|
172
176
|
## 🚀 Starting a New Project with `/lets-build`
|
|
173
177
|
|
|
174
|
-
This repository serves as an **
|
|
178
|
+
This repository serves as an **architecture governance toolkit, with an optional starter**. When beginning a new software project:
|
|
175
179
|
|
|
176
180
|
### Step 1: Initialize Workspace with npx
|
|
177
|
-
Pull and scaffold the complete
|
|
181
|
+
Pull and scaffold the complete architecture governance toolkit into your project directory using `npx`:
|
|
178
182
|
```bash
|
|
179
183
|
npx azcodr my-new-project
|
|
180
184
|
cd my-new-project
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# Generated-Project Enforcement Analysis: What a Scaffolded Project Actually Gets
|
|
2
|
+
|
|
3
|
+
> **Status:** evidence, not prose. Every claim below was produced by running the
|
|
4
|
+
> scaffolder on 2026-10-08, not by reading the skills docs.
|
|
5
|
+
> **Answers the plan's open question:** which linters does a non-TypeScript
|
|
6
|
+
> project actually get — and whether the "executable architecture" promise holds
|
|
7
|
+
> outside azcodr's own repo.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Method
|
|
12
|
+
|
|
13
|
+
1. Ran `scaffold({ force: true, noGit: true })` from compiled `lib/` into a clean
|
|
14
|
+
temp dir; inventoried every emitted file.
|
|
15
|
+
2. Ran `bootstrap_workspace.sh . <topology> <language>` under Git Bash for
|
|
16
|
+
`backend+typescript`, `backend+python`, `cli+go`, `extension+typescript`,
|
|
17
|
+
`systems-library+c`; diffed the file lists.
|
|
18
|
+
3. Executed the scaffolded project's `.agents/scripts/agent_guard.js` with a
|
|
19
|
+
must-block payload (`rm -rf /`), with the azcodr repo itself as control.
|
|
20
|
+
|
|
21
|
+
## Finding 1: No deterministic linter in any generated project
|
|
22
|
+
|
|
23
|
+
The scaffolded project contains exactly one toolchain-adjacent file:
|
|
24
|
+
`.editorconfig`. Its `package.json` lint script is:
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
"lint": "echo \"No linter configured yet. Run /lets-build to configure toolchain.\""
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The polyglot fitness-function table (`docs/rules/clean_code.md`, § Polyglot
|
|
31
|
+
Fitness Function Standards: ESLint / ruff / clippy / golangci-lint /
|
|
32
|
+
Checkstyle / Roslyn) ships as **documentation only**. Phase 4 step 3 of
|
|
33
|
+
`lets-build/SKILL.md` instructs the *agent* to "generate … deterministic
|
|
34
|
+
linter configurations" — i.e. enforcement in a user's project is agent-authored
|
|
35
|
+
and non-deterministic, exactly the gap the plan flagged. **A non-TypeScript
|
|
36
|
+
project gets no linter at all unless the agent writes one.**
|
|
37
|
+
|
|
38
|
+
## Finding 2: LANGUAGE does not change generated output
|
|
39
|
+
|
|
40
|
+
`backend+typescript` and `backend+python` produce byte-identical file lists
|
|
41
|
+
(18 files each: `.gitkeep` dirs, `tokens.json`, `smoke_test.sh`,
|
|
42
|
+
`workspace-profile.env`). `LANGUAGE` is validated and recorded but never
|
|
43
|
+
branches generation. Topology *does* scope directories (7–18 files by
|
|
44
|
+
topology). So: topology-scoping is deterministic; language support is a
|
|
45
|
+
recorded intention, not a generated artifact.
|
|
46
|
+
|
|
47
|
+
## Finding 3: The shipped runtime guard is inert in generated projects (fail-open)
|
|
48
|
+
|
|
49
|
+
`TEMPLATE_ITEMS` (`src/scaffold.ts`) ships `docs`, `.agents`, `scripts`,
|
|
50
|
+
`.github` — but **not** `src/`, `lib/`, or `bin/`. Consequences, all verified:
|
|
51
|
+
|
|
52
|
+
| Check | azcodr repo (control) | Scaffolded project |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| `.agents/scripts/agent_guard.js` vs `rm -rf /` payload | **BLOCKED** (exit 1) | **ALLOWED** (exit 0) |
|
|
55
|
+
| `lib/agent-guard.js` present | yes | **missing** — the guard's dynamic import rejects, and `main().catch(() => process.exit(0))` fails open |
|
|
56
|
+
| `.agents/hooks.json` references `agent_guard.js` | no | no |
|
|
57
|
+
| All hook blocks in `hooks.json` | `enabled: false` | `enabled: false` (shipped copy) |
|
|
58
|
+
|
|
59
|
+
The flagship "agent cannot skip" control therefore degrades, in a generated
|
|
60
|
+
project, to: an unwired hook script, with its engine missing, configured off,
|
|
61
|
+
that permits on error. The `boundaries` engine (`npx azcodr boundaries`)
|
|
62
|
+
likewise does not ship — only `scripts/validate-cli.js` (config/ledger/link
|
|
63
|
+
checks) is present.
|
|
64
|
+
|
|
65
|
+
## What *does* transfer deterministically
|
|
66
|
+
|
|
67
|
+
- 28 rule docs, 6 skills, `AGENTS.md` parity pointers, empty ADR ledger
|
|
68
|
+
(`data/memory.template`), `safety_guard.sh` + `verify_completion.sh` (present
|
|
69
|
+
but disabled in `hooks.json`), `smoke_test.sh` placeholder, topology-scoped
|
|
70
|
+
directories, `tokens.json` (backend/web only), `.azcodr/workspace-profile.env`.
|
|
71
|
+
|
|
72
|
+
## Recommendations (product decisions, not taken here)
|
|
73
|
+
|
|
74
|
+
1. **Either ship the engine or stop implying it ships.** Options: (a) add a
|
|
75
|
+
dependency on the published `azcodr` package + `lib/`- backed guard entry in
|
|
76
|
+
the starter `package.json`; (b) vendor a self-contained guard bundle into
|
|
77
|
+
`.agents/scripts/` with no `../../lib` import; (c) reword SKILL.md Phase 4
|
|
78
|
+
step 3 from "generate deterministic linter configurations" to an explicit
|
|
79
|
+
agent-authored checklist with verification (`npm run lint` must fail before
|
|
80
|
+
configs land, not echo).
|
|
81
|
+
2. **Fail closed, and wire the hook.** `agent_guard.js`'s catch-all `exit(0)`
|
|
82
|
+
is the wrong default for a security boundary; and `hooks.json` should ship
|
|
83
|
+
with at least the safety guard enabled plus a setup step that proves the
|
|
84
|
+
harness honors it (harnesses ignore unknown/unenabled hooks silently).
|
|
85
|
+
3. **Make LANGUAGE generative or say it isn't.** Either branch
|
|
86
|
+
`bootstrap_workspace.sh` per language (build manifests + pinned linter
|
|
87
|
+
configs per `clean_code.md` table) or change the skill wording to record the
|
|
88
|
+
language decision for the agent to implement in Phase 4.
|
|
89
|
+
4. **Close the benchmark gap this reveals.** `benchmark/RESULTS.md` Arm B/C
|
|
90
|
+
assume working hooks + boundary engine; in a real scaffolded project neither
|
|
91
|
+
is present. A fourth arm — *raw scaffolded project, no agent-authored
|
|
92
|
+
additions* — would measure the actual out-of-box enforcement: expect 0/3
|
|
93
|
+
traps caught.
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Addendum (2026-10-08): findings fixed — verify, don't trust
|
|
98
|
+
|
|
99
|
+
The three findings above are closed by the enforcement change set; each fix
|
|
100
|
+
names the test that locks it:
|
|
101
|
+
|
|
102
|
+
1. **No deterministic linter → per-language gate configs emitted.**
|
|
103
|
+
`bootstrap_workspace.sh` now writes the pinned config for the recorded
|
|
104
|
+
language (`eslint.config.js`, `ruff.toml`, `clippy.toml`, `.golangci.yml`,
|
|
105
|
+
`checkstyle.xml`, `.editorconfig` CA block, `.clang-tidy`; `generic` emits
|
|
106
|
+
none). Locked by `tests/bootstrap-toolchain.test.ts` (9 tests); presence is
|
|
107
|
+
re-checked on every `validate` run by the new phase 7
|
|
108
|
+
(`scripts/validate/toolchain.js`, 100% covered).
|
|
109
|
+
2. **LANGUAGE decorative → generative for toolchain.**
|
|
110
|
+
`backend+typescript` and `backend+python` still share directory trees
|
|
111
|
+
(topology-scoping, by design) but now differ in emitted gate configs; the
|
|
112
|
+
profile feed (`readProfileLanguage`) drives the validator. The agent still
|
|
113
|
+
owns build manifests (naming decisions) plus tool install and the Phase 5
|
|
114
|
+
live-block proof (`lets-build/SKILL.md`).
|
|
115
|
+
3. **Fail-open guard → vendored engine + fail-closed install.**
|
|
116
|
+
The guard engine ships inside `.agents/` (`.agents/lib/`, byte-locked to
|
|
117
|
+
`lib/` output by test), `agent_guard.js` resolves vendored-first, and a
|
|
118
|
+
missing engine exits 2 naming every path tried. Unparseable *payloads*
|
|
119
|
+
still fail open inside `inspectPreTool` per ADR-005 (ambiguous input, not a
|
|
120
|
+
broken install). `hooks.json` (+`.example`) wires an `architectural-guard`
|
|
121
|
+
PreToolUse entry. Locked by the three new `tests/agent-guard.test.ts` cases.
|
|
122
|
+
4. **Benchmark assumes working hooks → Arm D measures the scaffold.**
|
|
123
|
+
`runArmD()` + `auditScaffoldEnforcement()` score a real scaffold before and
|
|
124
|
+
after bootstrap; `benchmark/RESULTS.md` §5 carries the scorecard.
|
|
125
|
+
5. **Echo lint placeholder → rewired for Node profiles.** `bootstrap` points
|
|
126
|
+
the starter `lint` script at the emitted `eslint.config.js` (placeholder
|
|
127
|
+
only, never agent-wired entries; skipped when node is unavailable), so
|
|
128
|
+
`npm run lint` fails until the pinned tool lands instead of echoing
|
|
129
|
+
success.
|
|
130
|
+
|
|
131
|
+
## Reproduce
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
# 1. Scaffold inventory
|
|
135
|
+
node -e "const {scaffold}=require('./lib/scaffold.js'); scaffold({targetDir:'<tmp>',force:true,noGit:true})"
|
|
136
|
+
# 2. Topology x language matrix (Git Bash)
|
|
137
|
+
bash .agents/skills/lets-build/scripts/bootstrap_workspace.sh <tmp> backend python
|
|
138
|
+
# 3. Guard fail-open proof
|
|
139
|
+
echo '{"tool_name":"run_command","tool_input":{"command":"rm -rf /"}}' \
|
|
140
|
+
| node <scaffold>/.agents/scripts/agent_guard.js; echo "exit=$?"
|
|
141
|
+
```
|
package/docs/rules/clean_code.md
CHANGED
|
@@ -57,13 +57,13 @@ Before writing production code for any new feature or user story, the agent must
|
|
|
57
57
|
Prevent AI-generated code rot using automated fitness functions integrated into linting and continuous verification:
|
|
58
58
|
- **File Length Gates**: Maximum 250–300 lines per file (ESLint `max-lines`).
|
|
59
59
|
- **Function Length Gates**: Maximum 20–30 lines per function (ESLint `max-lines-per-function`).
|
|
60
|
-
- **Dependency Direction Gates**: Enforce unidirectional import rules (
|
|
60
|
+
- **Dependency Direction & Boundary Gates**: Enforce unidirectional import rules and cycle prevention natively with Azcodr's zero-dependency boundary linter (`npx azcodr boundaries [dir]`) or polyglot equivalents (ArchUnit, dependency-cruiser) ensuring domain core never imports infrastructure or transport adapters, and circular dependencies are forbidden.
|
|
61
61
|
- **Complexity Budgets**: Enforce cyclomatic complexity limits (maximum 10 per function).
|
|
62
62
|
If an AI attempt to add code violates any fitness function, the build fails immediately, blocking completion until the architecture is refactored.
|
|
63
63
|
|
|
64
64
|
### Polyglot Fitness Function Standards
|
|
65
65
|
|
|
66
|
-
When bootstrapping
|
|
66
|
+
When bootstrapping projects via `/lets-build`, `bootstrap_workspace.sh` emits the pinned gate configuration for the recorded language (never invented, never absent); the agent then installs the named tool, wires the project's lint entry to it, and proves the gate in Phase 5. Gates must match these exact rules:
|
|
67
67
|
|
|
68
68
|
| Language | Linter / Static Tool | Max File Lines (300) | Max Function Lines (30) | Max Complexity (10) | Max Arguments (3) |
|
|
69
69
|
|---|---|---|---|---|---|
|
|
@@ -73,3 +73,4 @@ When bootstrapping non-TypeScript projects via `/lets-build`, the agent must ins
|
|
|
73
73
|
| **Go** | `golangci-lint` | `maintidx: 300` | `funlen: lines = 30` | `gocyclo: min-complexity = 10` | `funlen: statements = 25` |
|
|
74
74
|
| **Java** | `Checkstyle` + `ArchUnit` | `FileLength: max 300` | `MethodLength: max 30` | `CyclomaticComplexity: max 10` | `ParameterNumber: max 3` |
|
|
75
75
|
| **C# / .NET** | `.editorconfig` + Roslyn | `file_length = 300:error` | `method_length = 30:error` | `CA1502: Avoid excessive complexity <= 10` | `CA1501: max 3 params` |
|
|
76
|
+
| **C / C++** | `clang-tidy` | project lint entry (no native check) | `readability-function-size LineThreshold = 30` | `readability-function-cognitive-complexity Threshold = 10` | `readability-function-size ParameterThreshold = 3` |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Test-Driven Development (London School TDD) & Test Isolation Standards
|
|
2
2
|
|
|
3
|
-
> **Core Mandate:** Drive all features through the non-negotiable 5-Phase Agile Domain Lifecycle (Outside-In Double-Loop TDD, Uncle Bob's 3 Laws), enforcing transactional database rollback per test, zero-sleep determinism, and
|
|
3
|
+
> **Core Mandate:** Drive all features through the non-negotiable 5-Phase Agile Domain Lifecycle (Outside-In Double-Loop TDD, Uncle Bob's 3 Laws), enforcing transactional database rollback per test, zero-sleep determinism, and CI-enforced 100% coverage on all metrics plus mutation floor (assertion-free tests rejected).
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -14,7 +14,7 @@ flowchart TD
|
|
|
14
14
|
P2["Phase 2: Tactical Domain Analysis<br/>• Ubiquitous Language & Bounded Contexts<br/>• Aggregate Roots & Business Invariants"]
|
|
15
15
|
P3["Phase 3: Outer Acceptance Test (RED)<br/>• Failing UI component or API route test<br/>• Verifies failure for expected reason"]
|
|
16
16
|
P4["Phase 4: Inner TDD & Collaborator Discovery (RED-GREEN-REFACTOR)<br/>• Discovers Use Cases & Ports<br/>• Nano-cycles with Uncle Bob's 3 Laws"]
|
|
17
|
-
P5["Phase 5: Outer Verification & Proof (GREEN)<br/>• Outer test passes with zero regressions<br/>• 100
|
|
17
|
+
P5["Phase 5: Outer Verification & Proof (GREEN)<br/>• Outer test passes with zero regressions<br/>• 100% coverage on all metrics + mutation gate & boundary smoke verification"]
|
|
18
18
|
|
|
19
19
|
P1 --> P2 --> P3 --> P4 --> P5
|
|
20
20
|
```
|
|
@@ -125,10 +125,13 @@ sequenceDiagram
|
|
|
125
125
|
|
|
126
126
|
---
|
|
127
127
|
|
|
128
|
-
## 7. Mandatory 100
|
|
128
|
+
## 7. Mandatory 100% Coverage on All Metrics + Mutation Floor (No Assertion-Free Tests)
|
|
129
129
|
|
|
130
|
-
- **Strict
|
|
131
|
-
- **
|
|
130
|
+
- **Strict 100% thresholds:** CI fails unless line, function, branch, AND statement coverage are all 100% (`jest.config.js`: lines 100 / functions 100 / branches 100 / statements 100). Generated projects must hold the same 100% bar in their own stack via the per-language gates in `docs/rules/clean_code.md`. Lowering any metric requires an ADR.
|
|
131
|
+
- **Mutation floor on guards:** line coverage alone proves little. Behavioral guards (`agent-guard-*`, `boundaries.ts`, protected-target checks) must hold a 100% mutant-kill gate (`npm run test:mutation`, `benchmark/mutation-test.js`). A green suite that lets a `blocked: true → false` or `return cycles → return []` mutant survive is a failure, even at 100% lines.
|
|
132
|
+
- **Assertion-density check:** agents asked to hit a line number write assertion-free tests to cover lines. Reject that explicitly: every new behavioral test must contain at least one behavior-asserting expectation (no `expect(true).toBe(true)`, no tests without assertions). Prefer `benchmark/evaluate.js --strict-tests` style checks (assertions per test ≥ 1, critical paths ≥ 2) over raising the line percentage.
|
|
133
|
+
- **What this repo enforces vs what users get:** this repo's exact thresholds live in `jest.config.js` (100% lines, branches, functions, statements) and are locked by `tests/coverage-config.test.ts`. Generated polyglot projects get the equivalent per-language fitness gates in `docs/rules/clean_code.md` (ESLint / ruff / clippy / golangci-lint / Checkstyle / Roslyn) at the same 100% bar — the tooling differs by stack, the bar must not.
|
|
134
|
+
- **Exhaustive status codes & error branches:** explicitly test all HTTP/gRPC response codes:
|
|
132
135
|
- Success: `200 OK`, `201 Created`, `204 No Content`
|
|
133
136
|
- Client Errors: `400 Bad Request`, `401 Unauthorized`, `403 Forbidden`, `404 Not Found`, `409 Conflict`, `422 Unprocessable Entity`, `429 Too Many Requests`
|
|
134
137
|
- Server Failures: `500 Internal Server Error`, `503 Service Unavailable`
|
package/lib/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module @azcodr/azcodr
|
|
3
|
-
*
|
|
3
|
+
* Architecture governance toolkit, with an optional starter.
|
|
4
4
|
*
|
|
5
5
|
* Provides problem-first, topology-aligned project scaffolding with strict systemic atomicity,
|
|
6
6
|
* filesystem scope guards, git worktree initialization, and multi-agent harness parity.
|
package/lib/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module @azcodr/azcodr
|
|
3
|
-
*
|
|
3
|
+
* Architecture governance toolkit, with an optional starter.
|
|
4
4
|
*
|
|
5
5
|
* Provides problem-first, topology-aligned project scaffolding with strict systemic atomicity,
|
|
6
6
|
* filesystem scope guards, git worktree initialization, and multi-agent harness parity.
|
package/lib/scaffold.js
CHANGED
|
@@ -126,7 +126,7 @@ function starterPackageJson(resolvedTarget) {
|
|
|
126
126
|
version: '0.1.0',
|
|
127
127
|
type: 'module',
|
|
128
128
|
private: true,
|
|
129
|
-
description: 'Scaffolded with azcodr
|
|
129
|
+
description: 'Scaffolded with the azcodr architecture governance toolkit',
|
|
130
130
|
scripts: {
|
|
131
131
|
test: 'node --test',
|
|
132
132
|
'test:coverage': 'jest --coverage',
|
package/lib/scaffold.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../src/scaffold.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../src/scaffold.ts"],"names":[],"mappings":"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"}
|
package/memory.md
CHANGED
|
@@ -35,6 +35,8 @@
|
|
|
35
35
|
| [ADR-016](./memory.md#adr-016-standalone-architectural-validator-agent-runtime-hooks-boundary-engine--empirical-benchmark) | Standalone Architectural Validator, Agent Runtime Hooks, Boundary Engine & Empirical Benchmark | 2026-10-08 | ACCEPTED | [`agentic_configuration.md`](./docs/rules/agentic_configuration.md) |
|
|
36
36
|
| [ADR-017](./memory.md#adr-017-continuous-mutation-and-benchmark-ci-gates-oss-governance-and-reference-example) | Continuous Mutation & Benchmark CI Gates, OSS Governance & Reference Example | 2026-10-08 | ACCEPTED | [`devops_ci_cd.md`](./docs/rules/devops_ci_cd.md) |
|
|
37
37
|
| [ADR-018](./memory.md#adr-018-first-class-architectural-boundary-and-cycle-inspection-cli-subcommand) | First-Class Architectural Boundary and Cycle Inspection CLI Subcommand | 2026-10-08 | ACCEPTED | [`clean_code.md`](./docs/rules/clean_code.md) |
|
|
38
|
+
| [ADR-019](./memory.md#adr-019-honest-claims-100-percent-all-metrics-gate-and-benchmark-limits-on-the-record) | Honest claims, 100% all-metrics gate, and benchmark limits on the record | 2026-10-08 | ACCEPTED | [`test_driven_development.md`](./docs/rules/test_driven_development.md) |
|
|
39
|
+
| [ADR-020](./memory.md#adr-020-generated-projects-get-deterministic-enforcement) | Generated projects get deterministic enforcement | 2026-10-08 | ACCEPTED | [`clean_code.md`](./docs/rules/clean_code.md) |
|
|
38
40
|
|
|
39
41
|
---
|
|
40
42
|
|
|
@@ -96,6 +98,20 @@
|
|
|
96
98
|
- **Consequences:** ✅ Every PR and push automatically verifies 100% mutation resistance and benchmark assertions. ✅ Full open-source governance compliance achieved (`SECURITY.md`, `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`). ✅ Concrete, working reference example proves downstream architectural boundaries and Clean Code limits. ✅ 100.00% lines, functions, statements, and branches coverage maintained across all 40 test suites and 489 tests.
|
|
97
99
|
- **Enforced In:** `.github/workflows/ci.yml`, `benchmark/mutation-test.js`, `CODE_OF_CONDUCT.md`, `examples/clean-architecture-todo/`, `tests/example-project.test.ts`, `README.md`, `package.json`
|
|
98
100
|
|
|
101
|
+
#### ADR-020: Generated Projects Get Deterministic Enforcement
|
|
102
|
+
- **Date:** 2026-10-08 | **Status:** ACCEPTED
|
|
103
|
+
- **Context:** The generated-enforcement analysis (`docs/generated-enforcement-analysis.md`) proved three defects by execution: (1) zero deterministic linter configs in any generated project (starter `lint` is an echo placeholder), (2) LANGUAGE recorded but never generative, (3) the shipped `agent_guard.js` crashed on its missing `../../lib` engine and its catch-all `exit(0)` silently allowed everything — including `rm -rf /` — in scaffolded projects.
|
|
104
|
+
- **Decision:** (1) Vendor the closed guard-engine set (`agent-guard{,-command,-file,-tdd}.js`) into `.agents/lib/` (byte-locked to `lib/` output by test; re-copy after every build per `CONTRIBUTING.md`); the hook resolves vendored-first, repo-layout second, `AZCODR_GUARD_ENGINE` override first. (2) Fail CLOSED (exit 2, naming every path tried) only when the engine itself cannot load — a broken install, not an ambiguous input; unparseable tool payloads still fail open inside `inspectPreTool` per ADR-005. (3) `bootstrap_workspace.sh` emits the pinned per-language gate config (11 profiles; `generic` emits none; create-if-absent so re-runs never clobber); build manifests stay agent-authored (naming decisions), and the agent installs the tool plus proves the gate with a live block in Phase 5. (4) `hooks.json` (+`.example`) wires an `architectural-guard` PreToolUse entry, disabled by default like all hooks — auto-enabling Stop/lint hooks in a dependency-less fresh scaffold would brick agent loops. (5) New validator phase 7 checks gate-config presence from `.azcodr/workspace-profile.env`, skipping cleanly without a profile (this repo unaffected). (6) Benchmark Arm D (`runArmD` + `auditScaffoldEnforcement`) scores a real scaffold before/after bootstrap; agent-layer state (tool installed, lint wired, hooks on) is reported, never gating.
|
|
105
|
+
- **Consequences:** ✅ Same `rm -rf /` payload now BLOCKED from vendored layout; missing engine exits 2 instead of allowing. ✅ Every recorded language produces its gate config; `validate` fails when one is deleted. ✅ 100% coverage holds on the new `scripts/validate/toolchain.js`. ⚠️ Hooks still require harness opt-in; tool installation still requires network at agent time — both recorded openly in `benchmark/RESULTS.md` §5–8.
|
|
106
|
+
- **Enforced In:** `.agents/lib/`, `.agents/scripts/agent_guard.js`, `.agents/hooks.json`, `.agents/skills/lets-build/scripts/bootstrap_workspace.sh`, `scripts/validate/toolchain.js`, `benchmark/evaluate.js`, `benchmark/run-benchmark.js`, `tests/agent-guard.test.ts`, `tests/bootstrap-toolchain.test.ts`, `tests/validate-toolchain.test.ts`, `tests/benchmark.test.ts`
|
|
107
|
+
|
|
108
|
+
#### ADR-019: Honest Claims, 100% All-Metrics Gate, and Benchmark Limits on the Record
|
|
109
|
+
- **Date:** 2026-10-08 | **Status:** ACCEPTED
|
|
110
|
+
- **Context:** An outside consolidated review found the engineering real but several claims ahead of evidence: "Enterprise starter" positioning vs a governance toolkit, coverage badges without a linked report, and a benchmark that proved drift reduction on synthetic fixtures while reading as a live-agent trial. Separately, the Jest gate enforced 100% lines but only 97% functions / 98% branches even though actual coverage measured 100/100/100/100.
|
|
111
|
+
- **Decision:** (1) Reposition wording to "Architecture governance toolkit, with an optional starter" (`package.json`, `jsr.json`, `AGENTS.md`, `README.md`, `src/index.ts`). (2) Raise the Jest gate to 100% lines, functions, branches, AND statements, locked by `tests/coverage-config.test.ts`; badges link to the CI workflow and the `coverage-report` artifact; docs keep the 100% bar AND the mutation floor plus assertion-density rejection so the number cannot be gamed with assertion-free tests. (3) Harden the benchmark honestly instead of inflating it: `evaluate.js` adds a test-quality proxy and marks cost metrics as manual-only, `run-benchmark.js` writes timestamped JSON transcripts to `benchmark/results/` and prints limitations, `RESULTS.md` gains "Where Azcodr Loses" and "Threats to validity" sections.
|
|
112
|
+
- **Consequences:** ✅ Claims match evidence; the gate is stricter than reality required (no new tests needed — 503 already pass at 100%). ✅ Every benchmark citation now ships with a transcript and a limits section. ⚠️ Synthetic single-run limits remain (no multi-model variance, no token/review-minute measurement) — recorded openly rather than fixed.
|
|
113
|
+
- **Enforced In:** `jest.config.js`, `tests/coverage-config.test.ts`, `benchmark/evaluate.js`, `benchmark/run-benchmark.js`, `benchmark/RESULTS.md`, `README.md`, `CONTRIBUTING.md`, `docs/rules/test_driven_development.md`
|
|
114
|
+
|
|
99
115
|
#### ADR-018: First-Class Architectural Boundary and Cycle Inspection CLI Subcommand
|
|
100
116
|
- **Date:** 2026-10-08 | **Status:** ACCEPTED
|
|
101
117
|
- **Context:** Following the implementation of the zero-dependency boundary and cycle engine (`src/boundaries.ts`, ADR-016), inspecting dependency cycles and Clean Architecture layer breaches required either running the empirical benchmark or importing the internal TypeScript engine programmatically. Users and CI pipelines lacked a direct, dedicated CLI entry point (`npx azcodr boundaries [dir]`) to run boundary and circular dependency inspections as a standalone architectural linter.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "azcodr",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2.5.1",
|
|
4
|
+
"description": "Architecture governance toolkit with topology-scoped scaffolding for AI-assisted development (early release)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"azcodr": "bin/azcodr.js"
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
export const TOOLCHAIN_FILES = {
|
|
5
|
+
typescript: ['eslint.config.js'],
|
|
6
|
+
javascript: ['eslint.config.js'],
|
|
7
|
+
deno: ['eslint.config.js'],
|
|
8
|
+
bun: ['eslint.config.js'],
|
|
9
|
+
python: ['ruff.toml'],
|
|
10
|
+
rust: ['clippy.toml'],
|
|
11
|
+
go: ['.golangci.yml'],
|
|
12
|
+
java: ['checkstyle.xml'],
|
|
13
|
+
csharp: ['.editorconfig'],
|
|
14
|
+
cpp: ['.clang-tidy'],
|
|
15
|
+
c: ['.clang-tidy'],
|
|
16
|
+
generic: []
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export function readProfileLanguage(workspaceRoot) {
|
|
20
|
+
const profile = path.join(workspaceRoot, '.azcodr', 'workspace-profile.env');
|
|
21
|
+
if (!fs.existsSync(profile)) return null;
|
|
22
|
+
const match = /^language=(.+)$/m.exec(fs.readFileSync(profile, 'utf-8'));
|
|
23
|
+
return match ? match[1].trim() : null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function expectedToolchainFiles(language) {
|
|
27
|
+
if (!language) return [];
|
|
28
|
+
return TOOLCHAIN_FILES[language] || [];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function reportMissing(ctx, language, expected) {
|
|
32
|
+
let missing = 0;
|
|
33
|
+
for (const name of expected) {
|
|
34
|
+
if (!fs.existsSync(path.join(ctx.workspaceRoot, name))) {
|
|
35
|
+
ctx.fail(`Missing toolchain gate '${name}' for language '${language}'; re-run bootstrap or restore the config.`);
|
|
36
|
+
missing += 1;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return missing;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function phaseToolchain(ctx) {
|
|
43
|
+
ctx.log('');
|
|
44
|
+
ctx.heading('7. Checking Toolchain Enforcement...');
|
|
45
|
+
const language = readProfileLanguage(ctx.workspaceRoot);
|
|
46
|
+
if (language === null) {
|
|
47
|
+
ctx.pass('No workspace profile; toolchain check skipped.');
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const expected = expectedToolchainFiles(language);
|
|
51
|
+
if (expected.length === 0) {
|
|
52
|
+
ctx.pass(`Language '${language}' declares no toolchain configs; nothing to enforce.`);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (reportMissing(ctx, language, expected) === 0) {
|
|
56
|
+
ctx.pass(`Toolchain gates present for language '${language}' (${expected.join(', ')}).`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export default { TOOLCHAIN_FILES, readProfileLanguage, expectedToolchainFiles, phaseToolchain };
|
package/scripts/validate.js
CHANGED
|
@@ -11,6 +11,7 @@ import { phaseRootConfig } from './validate/root.js';
|
|
|
11
11
|
import { phaseRules } from './validate/rules.js';
|
|
12
12
|
import { phaseSkills } from './validate/skills.js';
|
|
13
13
|
import { phaseLinks } from './validate/links.js';
|
|
14
|
+
import { phaseToolchain, readProfileLanguage, expectedToolchainFiles, TOOLCHAIN_FILES } from './validate/toolchain.js';
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* NOTE: this module is a library. It never self-executes, so importing it from
|
|
@@ -105,6 +106,7 @@ function runValidation(workspaceRoot, reporter = createReporter()) {
|
|
|
105
106
|
phaseLinks(ctx);
|
|
106
107
|
phaseMemory(ctx);
|
|
107
108
|
phaseLedger(ctx);
|
|
109
|
+
phaseToolchain(ctx);
|
|
108
110
|
reportSummary(counts, reporter);
|
|
109
111
|
return {
|
|
110
112
|
errors: counts.errors,
|
|
@@ -138,7 +140,11 @@ export {
|
|
|
138
140
|
createLowercaseParityLink,
|
|
139
141
|
stripHtmlComments,
|
|
140
142
|
stripFencedCode,
|
|
141
|
-
walkMarkdown
|
|
143
|
+
walkMarkdown,
|
|
144
|
+
phaseToolchain,
|
|
145
|
+
readProfileLanguage,
|
|
146
|
+
expectedToolchainFiles,
|
|
147
|
+
TOOLCHAIN_FILES
|
|
142
148
|
};
|
|
143
149
|
|
|
144
150
|
export default {
|
|
@@ -154,5 +160,9 @@ export default {
|
|
|
154
160
|
createLowercaseParityLink,
|
|
155
161
|
stripHtmlComments,
|
|
156
162
|
stripFencedCode,
|
|
157
|
-
walkMarkdown
|
|
163
|
+
walkMarkdown,
|
|
164
|
+
phaseToolchain,
|
|
165
|
+
readProfileLanguage,
|
|
166
|
+
expectedToolchainFiles,
|
|
167
|
+
TOOLCHAIN_FILES
|
|
158
168
|
};
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module @azcodr/azcodr
|
|
3
|
-
*
|
|
3
|
+
* Architecture governance toolkit, with an optional starter.
|
|
4
4
|
*
|
|
5
5
|
* Provides problem-first, topology-aligned project scaffolding with strict systemic atomicity,
|
|
6
6
|
* filesystem scope guards, git worktree initialization, and multi-agent harness parity.
|
package/src/scaffold.ts
CHANGED
|
@@ -184,7 +184,7 @@ function starterPackageJson(resolvedTarget: string): Record<string, unknown> {
|
|
|
184
184
|
version: '0.1.0',
|
|
185
185
|
type: 'module',
|
|
186
186
|
private: true,
|
|
187
|
-
description: 'Scaffolded with azcodr
|
|
187
|
+
description: 'Scaffolded with the azcodr architecture governance toolkit',
|
|
188
188
|
scripts: {
|
|
189
189
|
test: 'node --test',
|
|
190
190
|
'test:coverage': 'jest --coverage',
|