azcodr 2.5.1 → 2.7.0
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 +7 -7
- package/.agents/hooks.json.example +8 -8
- package/.agents/lib/boundaries.js +158 -0
- package/.agents/scripts/boundary_guard.js +93 -0
- package/.agents/scripts/desktop_doctor.js +108 -0
- package/.agents/scripts/safety_guard.js +82 -0
- package/.agents/scripts/smoke_test.js +78 -0
- package/.agents/scripts/verify_completion.js +76 -0
- package/.agents/skills/lets-build/scripts/bootstrap_workspace.sh +13 -0
- package/.gitignore +1 -0
- package/CONTRIBUTING.md +2 -2
- package/README.md +6 -8
- package/docs/generated-enforcement-analysis.md +21 -0
- package/lib/scaffold.d.ts.map +1 -1
- package/lib/scaffold.js +4 -0
- package/lib/scaffold.js.map +1 -1
- package/lib/starter-ci.d.ts +29 -0
- package/lib/starter-ci.d.ts.map +1 -0
- package/lib/starter-ci.js +59 -0
- package/lib/starter-ci.js.map +1 -0
- package/memory.md +16 -0
- package/package.json +3 -3
- package/src/scaffold.ts +4 -0
- package/src/starter-ci.ts +61 -0
package/.agents/hooks.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"safety-guard": {
|
|
3
|
-
"enabled":
|
|
3
|
+
"enabled": true,
|
|
4
4
|
"PreToolUse": [
|
|
5
5
|
{
|
|
6
6
|
"matcher": "run_command",
|
|
7
7
|
"hooks": [
|
|
8
8
|
{
|
|
9
9
|
"type": "command",
|
|
10
|
-
"command": "
|
|
10
|
+
"command": "node .agents/scripts/safety_guard.js",
|
|
11
11
|
"timeout": 15
|
|
12
12
|
}
|
|
13
13
|
]
|
|
@@ -15,14 +15,14 @@
|
|
|
15
15
|
]
|
|
16
16
|
},
|
|
17
17
|
"architectural-guard": {
|
|
18
|
-
"enabled":
|
|
18
|
+
"enabled": true,
|
|
19
19
|
"PreToolUse": [
|
|
20
20
|
{
|
|
21
21
|
"matcher": "run_command",
|
|
22
22
|
"hooks": [
|
|
23
23
|
{
|
|
24
24
|
"type": "command",
|
|
25
|
-
"command": "
|
|
25
|
+
"command": "node .agents/scripts/agent_guard.js",
|
|
26
26
|
"timeout": 15
|
|
27
27
|
}
|
|
28
28
|
]
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
]
|
|
31
31
|
},
|
|
32
32
|
"post-tool-lint": {
|
|
33
|
-
"enabled":
|
|
33
|
+
"enabled": true,
|
|
34
34
|
"PostToolUse": [
|
|
35
35
|
{
|
|
36
36
|
"matcher": "run_command",
|
|
@@ -45,11 +45,11 @@
|
|
|
45
45
|
]
|
|
46
46
|
},
|
|
47
47
|
"stop-verifier": {
|
|
48
|
-
"enabled":
|
|
48
|
+
"enabled": true,
|
|
49
49
|
"Stop": [
|
|
50
50
|
{
|
|
51
51
|
"type": "command",
|
|
52
|
-
"command": "
|
|
52
|
+
"command": "node .agents/scripts/verify_completion.js",
|
|
53
53
|
"timeout": 120
|
|
54
54
|
}
|
|
55
55
|
]
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"safety-guard": {
|
|
3
|
-
"enabled":
|
|
3
|
+
"enabled": true,
|
|
4
4
|
"PreToolUse": [
|
|
5
5
|
{
|
|
6
6
|
"matcher": "run_command",
|
|
7
7
|
"hooks": [
|
|
8
8
|
{
|
|
9
9
|
"type": "command",
|
|
10
|
-
"command": "
|
|
10
|
+
"command": "node .agents/scripts/safety_guard.js",
|
|
11
11
|
"timeout": 15
|
|
12
12
|
}
|
|
13
13
|
]
|
|
@@ -15,14 +15,14 @@
|
|
|
15
15
|
]
|
|
16
16
|
},
|
|
17
17
|
"architectural-guard": {
|
|
18
|
-
"enabled":
|
|
18
|
+
"enabled": true,
|
|
19
19
|
"PreToolUse": [
|
|
20
20
|
{
|
|
21
21
|
"matcher": "run_command",
|
|
22
22
|
"hooks": [
|
|
23
23
|
{
|
|
24
24
|
"type": "command",
|
|
25
|
-
"command": "
|
|
25
|
+
"command": "node .agents/scripts/agent_guard.js",
|
|
26
26
|
"timeout": 15
|
|
27
27
|
}
|
|
28
28
|
]
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
]
|
|
31
31
|
},
|
|
32
32
|
"post-tool-lint": {
|
|
33
|
-
"enabled":
|
|
33
|
+
"enabled": true,
|
|
34
34
|
"PostToolUse": [
|
|
35
35
|
{
|
|
36
36
|
"matcher": "run_command",
|
|
@@ -45,12 +45,12 @@
|
|
|
45
45
|
]
|
|
46
46
|
},
|
|
47
47
|
"stop-verifier": {
|
|
48
|
-
"enabled":
|
|
48
|
+
"enabled": true,
|
|
49
49
|
"Stop": [
|
|
50
50
|
{
|
|
51
51
|
"type": "command",
|
|
52
|
-
"command": "
|
|
53
|
-
"timeout":
|
|
52
|
+
"command": "node .agents/scripts/verify_completion.js",
|
|
53
|
+
"timeout": 120
|
|
54
54
|
}
|
|
55
55
|
]
|
|
56
56
|
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
export const DEFAULT_BOUNDARY_RULES = [
|
|
4
|
+
{
|
|
5
|
+
fromLayer: 'domain',
|
|
6
|
+
disallowImportsFrom: ['infrastructure', 'infra', 'adapters', 'cli', 'transport', 'controllers']
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
fromLayer: 'core',
|
|
10
|
+
disallowImportsFrom: ['infrastructure', 'infra', 'adapters', 'presentation', 'ui']
|
|
11
|
+
}
|
|
12
|
+
];
|
|
13
|
+
function isSkippedDir(name) {
|
|
14
|
+
return name.startsWith('.') || name === 'node_modules' || name === 'lib';
|
|
15
|
+
}
|
|
16
|
+
function isEligibleSource(name) {
|
|
17
|
+
const isScript = name.endsWith('.ts') || name.endsWith('.js');
|
|
18
|
+
if (!isScript || name.endsWith('.d.ts'))
|
|
19
|
+
return false;
|
|
20
|
+
return !name.includes('.test.') && !name.includes('.spec.');
|
|
21
|
+
}
|
|
22
|
+
export function findSourceFiles(dir) {
|
|
23
|
+
if (!fs.existsSync(dir))
|
|
24
|
+
return [];
|
|
25
|
+
const results = [];
|
|
26
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
27
|
+
for (const entry of entries) {
|
|
28
|
+
if (isSkippedDir(entry.name))
|
|
29
|
+
continue;
|
|
30
|
+
const full = path.join(dir, entry.name);
|
|
31
|
+
if (entry.isDirectory()) {
|
|
32
|
+
results.push(...findSourceFiles(full));
|
|
33
|
+
}
|
|
34
|
+
else if (isEligibleSource(entry.name)) {
|
|
35
|
+
results.push(path.resolve(full));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return results.sort();
|
|
39
|
+
}
|
|
40
|
+
function resolveImportTarget(sourceFile, specifier) {
|
|
41
|
+
const dir = path.dirname(sourceFile);
|
|
42
|
+
const candidate = path.resolve(dir, specifier);
|
|
43
|
+
if (fs.existsSync(candidate))
|
|
44
|
+
return candidate;
|
|
45
|
+
if (specifier.endsWith('.js')) {
|
|
46
|
+
const tsCandidate = path.resolve(dir, specifier.slice(0, -3) + '.ts');
|
|
47
|
+
if (fs.existsSync(tsCandidate))
|
|
48
|
+
return tsCandidate;
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
export function extractLocalImports(filePath) {
|
|
53
|
+
if (!fs.existsSync(filePath))
|
|
54
|
+
return [];
|
|
55
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
56
|
+
const imports = [];
|
|
57
|
+
const regex = /(?:import|export)\s+(?:(?:[\s\S]*?from\s+)|)['"](\.[^'"]+)['"]|import\s*\(\s*['"](\.[^'"]+)['"]\s*\)/g;
|
|
58
|
+
for (const match of content.matchAll(regex)) {
|
|
59
|
+
const spec = match[1] ?? match[2];
|
|
60
|
+
if (spec) {
|
|
61
|
+
const resolved = resolveImportTarget(filePath, spec);
|
|
62
|
+
if (resolved && !imports.includes(resolved)) {
|
|
63
|
+
imports.push(resolved);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return imports.sort();
|
|
68
|
+
}
|
|
69
|
+
export function buildDependencyGraph(files) {
|
|
70
|
+
const graph = new Map();
|
|
71
|
+
for (const file of files) {
|
|
72
|
+
graph.set(file, extractLocalImports(file));
|
|
73
|
+
}
|
|
74
|
+
return graph;
|
|
75
|
+
}
|
|
76
|
+
export function detectDependencyCycles(graph) {
|
|
77
|
+
const cycles = [];
|
|
78
|
+
const visited = new Set();
|
|
79
|
+
const inStack = new Set();
|
|
80
|
+
const pathStack = [];
|
|
81
|
+
function dfs(node) {
|
|
82
|
+
visited.add(node);
|
|
83
|
+
inStack.add(node);
|
|
84
|
+
pathStack.push(node);
|
|
85
|
+
for (const neighbor of graph.get(node) ?? []) {
|
|
86
|
+
if (!visited.has(neighbor)) {
|
|
87
|
+
dfs(neighbor);
|
|
88
|
+
}
|
|
89
|
+
else if (inStack.has(neighbor)) {
|
|
90
|
+
const startIdx = pathStack.indexOf(neighbor);
|
|
91
|
+
if (startIdx !== -1) {
|
|
92
|
+
cycles.push([...pathStack.slice(startIdx), neighbor]);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
inStack.delete(node);
|
|
97
|
+
pathStack.pop();
|
|
98
|
+
}
|
|
99
|
+
for (const node of graph.keys()) {
|
|
100
|
+
if (!visited.has(node))
|
|
101
|
+
dfs(node);
|
|
102
|
+
}
|
|
103
|
+
return cycles;
|
|
104
|
+
}
|
|
105
|
+
function hasLayerSegment(pathStr, segment) {
|
|
106
|
+
return new RegExp(`(^|[/\\\\])${segment}([/\\\\]|$)`, 'i').test(pathStr);
|
|
107
|
+
}
|
|
108
|
+
export function detectBoundaryViolations(graph, rules = DEFAULT_BOUNDARY_RULES) {
|
|
109
|
+
const violations = [];
|
|
110
|
+
for (const [file, imports] of graph.entries()) {
|
|
111
|
+
for (const rule of rules) {
|
|
112
|
+
if (hasLayerSegment(file, rule.fromLayer)) {
|
|
113
|
+
for (const imported of imports) {
|
|
114
|
+
for (const disallowed of rule.disallowImportsFrom) {
|
|
115
|
+
if (hasLayerSegment(imported, disallowed)) {
|
|
116
|
+
violations.push({
|
|
117
|
+
file,
|
|
118
|
+
importedFile: imported,
|
|
119
|
+
fromLayer: rule.fromLayer,
|
|
120
|
+
toLayer: disallowed
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return violations;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Inspects a source tree for architectural boundary violations and circular dependency cycles.
|
|
132
|
+
*
|
|
133
|
+
* @param sourceDir - Directory containing source code (e.g. src/).
|
|
134
|
+
* @param rules - Architectural boundary rules.
|
|
135
|
+
* @returns Report summarizing cycle and boundary findings.
|
|
136
|
+
*/
|
|
137
|
+
export function inspectBoundaries(sourceDir, rules = DEFAULT_BOUNDARY_RULES) {
|
|
138
|
+
const files = findSourceFiles(sourceDir);
|
|
139
|
+
const graph = buildDependencyGraph(files);
|
|
140
|
+
const cycles = detectDependencyCycles(graph);
|
|
141
|
+
const violations = detectBoundaryViolations(graph, rules);
|
|
142
|
+
return {
|
|
143
|
+
moduleCount: files.length,
|
|
144
|
+
cycles,
|
|
145
|
+
violations,
|
|
146
|
+
ok: cycles.length === 0 && violations.length === 0
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
export default {
|
|
150
|
+
DEFAULT_BOUNDARY_RULES,
|
|
151
|
+
findSourceFiles,
|
|
152
|
+
extractLocalImports,
|
|
153
|
+
buildDependencyGraph,
|
|
154
|
+
detectDependencyCycles,
|
|
155
|
+
detectBoundaryViolations,
|
|
156
|
+
inspectBoundaries
|
|
157
|
+
};
|
|
158
|
+
//# sourceMappingURL=boundaries.js.map
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Cross-platform architectural boundary & cycle guard.
|
|
5
|
+
*
|
|
6
|
+
* Ships inside scaffolded projects so structural drift (circular imports and
|
|
7
|
+
* Clean/Hexagonal layer breaches) is machine-checked with zero third-party
|
|
8
|
+
* dependencies. The engine is vendored under `.agents/lib/` and byte-locked to
|
|
9
|
+
* `lib/boundaries.js`; this wrapper only resolves it and reports the verdict.
|
|
10
|
+
*/
|
|
11
|
+
import fs from 'node:fs';
|
|
12
|
+
import path from 'node:path';
|
|
13
|
+
import process from 'node:process';
|
|
14
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
15
|
+
|
|
16
|
+
const selfDir = path.dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
|
|
18
|
+
// Resolution order (first existing file wins):
|
|
19
|
+
// 1. AZCODR_BOUNDARY_ENGINE override (tests, embedders).
|
|
20
|
+
// 2. ../lib/boundaries.js — vendored engine inside `.agents/`
|
|
21
|
+
// (present in scaffolded projects; `.agents` is copied recursively).
|
|
22
|
+
// 3. ../../lib/boundaries.js — azcodr repo dev layout (fallback only).
|
|
23
|
+
const engineCandidates = [
|
|
24
|
+
process.env.AZCODR_BOUNDARY_ENGINE,
|
|
25
|
+
path.resolve(selfDir, '../lib/boundaries.js'),
|
|
26
|
+
path.resolve(selfDir, '../../lib/boundaries.js')
|
|
27
|
+
].filter(Boolean);
|
|
28
|
+
|
|
29
|
+
async function getEngine() {
|
|
30
|
+
const tried = [];
|
|
31
|
+
for (const candidate of engineCandidates) {
|
|
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 is a broken install, never a silent pass.
|
|
43
|
+
console.error(
|
|
44
|
+
'🚨 Boundary Guard misconfigured: engine module not found, refusing to fail open. Tried:\n' +
|
|
45
|
+
tried.map((t) => ` - ${t}`).join('\n')
|
|
46
|
+
);
|
|
47
|
+
process.exit(2);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function resolveTarget(positional) {
|
|
51
|
+
const explicit = positional[0];
|
|
52
|
+
if (explicit) return path.resolve(explicit);
|
|
53
|
+
const srcDir = path.resolve(process.cwd(), 'src');
|
|
54
|
+
return fs.existsSync(srcDir) ? srcDir : process.cwd();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function rel(p) {
|
|
58
|
+
return path.relative(process.cwd(), p) || '.';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function printReport(report) {
|
|
62
|
+
console.log('🔍 Inspecting Architectural Boundaries in: ' + rel(report.targetDir));
|
|
63
|
+
console.log('--------------------------------------------------------------');
|
|
64
|
+
if (report.ok) {
|
|
65
|
+
console.log(`✅ No cycles or boundary violations across ${report.moduleCount} module(s).`);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
for (const cycle of report.cycles) {
|
|
69
|
+
console.error(' - Circular Dependency: ' + cycle.map(rel).join(' -> '));
|
|
70
|
+
}
|
|
71
|
+
for (const v of report.violations) {
|
|
72
|
+
console.error(` - Layer Boundary Breach: ${rel(v.file)} imports ${rel(v.importedFile)} (${v.fromLayer} -> ${v.toLayer})`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function emit(report, silent) {
|
|
77
|
+
if (!silent) printReport(report);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function main() {
|
|
81
|
+
const args = process.argv.slice(2).filter((a) => a !== '--silent');
|
|
82
|
+
const silent = process.argv.includes('--silent');
|
|
83
|
+
const { inspectBoundaries } = await getEngine();
|
|
84
|
+
const target = resolveTarget(args);
|
|
85
|
+
const report = inspectBoundaries(target);
|
|
86
|
+
emit({ ...report, targetDir: target }, silent);
|
|
87
|
+
process.exit(report.ok ? 0 : 1);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
main().catch((err) => {
|
|
91
|
+
console.error(`🚨 Boundary Guard crashed: ${err?.message || err}`);
|
|
92
|
+
process.exit(2);
|
|
93
|
+
});
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Pre-flight Desktop Distribution Doctor (Milestone 4).
|
|
5
|
+
* Prevents hybrid desktop build defects: missing custom-protocol, hollow CSS, dev-url embedding.
|
|
6
|
+
*/
|
|
7
|
+
import fs from 'node:fs';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import process from 'node:process';
|
|
10
|
+
|
|
11
|
+
function findCargoToml(targetDir) {
|
|
12
|
+
const candidates = [
|
|
13
|
+
path.join(targetDir, 'Cargo.toml'),
|
|
14
|
+
path.join(targetDir, 'src-tauri', 'Cargo.toml')
|
|
15
|
+
];
|
|
16
|
+
for (const c of candidates) {
|
|
17
|
+
if (fs.existsSync(c)) return c;
|
|
18
|
+
}
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function verifyCargoTauri(cargoPath) {
|
|
23
|
+
const content = fs.readFileSync(cargoPath, 'utf-8');
|
|
24
|
+
if (!content.includes('tauri')) return { ok: true, skipped: true };
|
|
25
|
+
|
|
26
|
+
const hasProtocol = content.includes('custom-protocol');
|
|
27
|
+
if (!hasProtocol) {
|
|
28
|
+
return {
|
|
29
|
+
ok: false,
|
|
30
|
+
reason: "Missing 'custom-protocol' feature in Cargo.toml dependencies. Tauri release binaries cannot embed frontend dist assets without it."
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
return { ok: true, message: 'Custom protocol enabled in Cargo.toml.' };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function collectCssFiles(dir) {
|
|
37
|
+
const files = [];
|
|
38
|
+
if (!fs.existsSync(dir)) return files;
|
|
39
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
40
|
+
for (const entry of entries) {
|
|
41
|
+
const full = path.join(dir, entry.name);
|
|
42
|
+
if (entry.isDirectory()) files.push(...collectCssFiles(full));
|
|
43
|
+
else if (entry.name.endsWith('.css')) files.push(full);
|
|
44
|
+
}
|
|
45
|
+
return files;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function verifyCssDensity(targetDir, minBytes = 5000) {
|
|
49
|
+
const searchDirs = [
|
|
50
|
+
path.join(targetDir, 'dist'),
|
|
51
|
+
path.join(targetDir, 'client', 'dist'),
|
|
52
|
+
path.join(targetDir, 'build')
|
|
53
|
+
];
|
|
54
|
+
let foundAny = false;
|
|
55
|
+
let totalBytes = 0;
|
|
56
|
+
|
|
57
|
+
for (const dir of searchDirs) {
|
|
58
|
+
const cssList = collectCssFiles(dir);
|
|
59
|
+
if (cssList.length > 0) {
|
|
60
|
+
foundAny = true;
|
|
61
|
+
for (const f of cssList) totalBytes += fs.statSync(f).size;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!foundAny) return { ok: true, skipped: true };
|
|
66
|
+
if (totalBytes < minBytes) {
|
|
67
|
+
return {
|
|
68
|
+
ok: false,
|
|
69
|
+
reason: `Hollow CSS bundle detected (${totalBytes} bytes < ${minBytes} bytes). Check that postcss.config.js is configured for Tailwind.`
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
return { ok: true, message: `CSS bundle density verified (${totalBytes} bytes).` };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function main() {
|
|
76
|
+
const targetDir = path.resolve(process.argv[2] || process.cwd());
|
|
77
|
+
const cargoPath = findCargoToml(targetDir);
|
|
78
|
+
const cssResult = verifyCssDensity(targetDir);
|
|
79
|
+
|
|
80
|
+
if (!cargoPath && cssResult.skipped) {
|
|
81
|
+
console.log('ℹ️ No desktop artifacts found in: ' + targetDir);
|
|
82
|
+
process.exit(0);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
let failed = false;
|
|
86
|
+
console.log('🩺 Desktop Distribution Doctor inspecting: ' + targetDir);
|
|
87
|
+
|
|
88
|
+
if (cargoPath) {
|
|
89
|
+
const cargoRes = verifyCargoTauri(cargoPath);
|
|
90
|
+
if (!cargoRes.ok) {
|
|
91
|
+
console.error(`🚨 ${cargoRes.reason}`);
|
|
92
|
+
failed = true;
|
|
93
|
+
} else if (cargoRes.message) {
|
|
94
|
+
console.log(`✅ ${cargoRes.message}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (!cssResult.ok) {
|
|
99
|
+
console.error(`🚨 ${cssResult.reason}`);
|
|
100
|
+
failed = true;
|
|
101
|
+
} else if (cssResult.message) {
|
|
102
|
+
console.log(`✅ ${cssResult.message}`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
process.exit(failed ? 1 : 0);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
main();
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Cross-platform PreToolUse safety guard in pure Node.js.
|
|
5
|
+
* Blocks high-severity destructive shell commands (rm -rf, force push, drop db).
|
|
6
|
+
*/
|
|
7
|
+
import fs from 'node:fs';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import process from 'node:process';
|
|
10
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
11
|
+
|
|
12
|
+
const selfDir = path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
|
|
14
|
+
const candidates = [
|
|
15
|
+
process.env.AZCODR_GUARD_ENGINE,
|
|
16
|
+
path.resolve(selfDir, '../lib/agent-guard-command.js'),
|
|
17
|
+
path.resolve(selfDir, '../../lib/agent-guard-command.js')
|
|
18
|
+
].filter(Boolean);
|
|
19
|
+
|
|
20
|
+
async function getCommandModule() {
|
|
21
|
+
const tried = [];
|
|
22
|
+
for (const candidate of candidates) {
|
|
23
|
+
if (!fs.existsSync(candidate)) {
|
|
24
|
+
tried.push(candidate);
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
return await import(pathToFileURL(candidate).href);
|
|
29
|
+
} catch (err) {
|
|
30
|
+
tried.push(`${candidate} (${err.message})`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
console.error(
|
|
34
|
+
'🚨 Safety Guard misconfigured: command engine not found. Tried:\n' +
|
|
35
|
+
tried.map((t) => ` - ${t}`).join('\n')
|
|
36
|
+
);
|
|
37
|
+
process.exit(2);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function readStdin(timeoutMs = 1500) {
|
|
41
|
+
if (process.stdin.isTTY) return '';
|
|
42
|
+
return new Promise((resolve) => {
|
|
43
|
+
let acc = '';
|
|
44
|
+
const timer = setTimeout(() => resolve(acc), timeoutMs);
|
|
45
|
+
process.stdin.setEncoding('utf-8');
|
|
46
|
+
process.stdin.on('data', (chunk) => { acc += chunk; });
|
|
47
|
+
process.stdin.on('end', () => {
|
|
48
|
+
clearTimeout(timer);
|
|
49
|
+
resolve(acc);
|
|
50
|
+
});
|
|
51
|
+
process.stdin.on('error', () => {
|
|
52
|
+
clearTimeout(timer);
|
|
53
|
+
resolve(acc);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function main() {
|
|
59
|
+
const argvInput = process.argv.slice(2).join(' ').trim();
|
|
60
|
+
const stdinInput = (await readStdin()).trim();
|
|
61
|
+
const rawInput = argvInput || stdinInput;
|
|
62
|
+
|
|
63
|
+
if (!rawInput) {
|
|
64
|
+
process.exit(0);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const { inspectCommand } = await getCommandModule();
|
|
68
|
+
const decision = inspectCommand(rawInput);
|
|
69
|
+
|
|
70
|
+
if (decision.blocked) {
|
|
71
|
+
console.error(`🚨 Safety Guard: ${decision.reason}`);
|
|
72
|
+
console.error(` command: ${decision.command || rawInput.slice(0, 300)}`);
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
process.exit(0);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
main().catch((err) => {
|
|
80
|
+
console.error(`🚨 Safety Guard crashed: ${err?.message || err}`);
|
|
81
|
+
process.exit(2);
|
|
82
|
+
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Cross-platform Boundary & Environment Smoke Test (Milestone 3).
|
|
5
|
+
* Verifies boundaries and diagnoses port occupancy before dev launch.
|
|
6
|
+
*/
|
|
7
|
+
import fs from 'node:fs';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import net from 'node:net';
|
|
10
|
+
import process from 'node:process';
|
|
11
|
+
import { spawnSync } from 'node:child_process';
|
|
12
|
+
|
|
13
|
+
function parseMonitoredPorts(argv) {
|
|
14
|
+
const customIndex = argv.indexOf('--ports');
|
|
15
|
+
if (customIndex !== -1 && argv[customIndex + 1]) {
|
|
16
|
+
return argv[customIndex + 1].split(',').map((p) => parseInt(p.trim(), 10)).filter(Boolean);
|
|
17
|
+
}
|
|
18
|
+
return [5173, 3000, 4000, 8080];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function checkPort(port, host = '127.0.0.1') {
|
|
22
|
+
return new Promise((resolve) => {
|
|
23
|
+
const server = net.createServer();
|
|
24
|
+
server.once('error', (err) => {
|
|
25
|
+
resolve({ port, available: false, error: err.code });
|
|
26
|
+
});
|
|
27
|
+
server.once('listening', () => {
|
|
28
|
+
server.close(() => resolve({ port, available: true }));
|
|
29
|
+
});
|
|
30
|
+
server.listen(port, host);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function runPortDoctor(ports) {
|
|
35
|
+
console.log('🩺 Port doctor inspecting local dev ports...');
|
|
36
|
+
const results = await Promise.all(ports.map((p) => checkPort(p)));
|
|
37
|
+
for (const r of results) {
|
|
38
|
+
if (!r.available) {
|
|
39
|
+
console.log(`⚠️ Port ${r.port} is occupied (${r.error}). Check for background services to prevent collisions.`);
|
|
40
|
+
} else {
|
|
41
|
+
console.log(`✅ Port ${r.port} is free.`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function runBoundaryGuard(targetDir) {
|
|
47
|
+
const guardPath = path.resolve(process.cwd(), '.agents', 'scripts', 'boundary_guard.js');
|
|
48
|
+
if (!fs.existsSync(guardPath)) {
|
|
49
|
+
console.log('ℹ️ Boundary guard script not found, skipping static graph check.');
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
console.log('🔍 Boundary smoke verification running on: ' + targetDir);
|
|
53
|
+
const res = spawnSync('node', [guardPath, targetDir], { stdio: 'inherit', shell: true });
|
|
54
|
+
return res.status === 0;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function main() {
|
|
58
|
+
const positional = process.argv.slice(2).filter((a) => !a.startsWith('--'));
|
|
59
|
+
const targetDir = path.resolve(positional[0] || process.cwd());
|
|
60
|
+
const ports = parseMonitoredPorts(process.argv);
|
|
61
|
+
|
|
62
|
+
console.log('🚀 Running Full-Stack Smoke Verification...');
|
|
63
|
+
const boundaryPassed = runBoundaryGuard(targetDir);
|
|
64
|
+
await runPortDoctor(ports);
|
|
65
|
+
|
|
66
|
+
if (!boundaryPassed) {
|
|
67
|
+
console.error('🚨 Boundary verification failed!');
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
console.log('✅ Smoke verification passed cleanly!');
|
|
72
|
+
process.exit(0);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
main().catch((err) => {
|
|
76
|
+
console.error(`🚨 Smoke verification crashed: ${err?.message || err}`);
|
|
77
|
+
process.exit(2);
|
|
78
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Cross-platform Stop hook in pure Node.js.
|
|
5
|
+
* Blocks premature agent exit when verification gates (validate, coverage, test, lint) fail.
|
|
6
|
+
*/
|
|
7
|
+
import fs from 'node:fs';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import process from 'node:process';
|
|
10
|
+
import { spawnSync } from 'node:child_process';
|
|
11
|
+
|
|
12
|
+
function loadPackageJson() {
|
|
13
|
+
const pkgPath = path.resolve(process.cwd(), 'package.json');
|
|
14
|
+
if (!fs.existsSync(pkgPath)) return null;
|
|
15
|
+
try {
|
|
16
|
+
return JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
17
|
+
} catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function runNpmScript(name, label) {
|
|
23
|
+
console.error(label);
|
|
24
|
+
const result = spawnSync('npm', ['run', name, '--silent'], {
|
|
25
|
+
stdio: 'inherit',
|
|
26
|
+
shell: true
|
|
27
|
+
});
|
|
28
|
+
return result.status === 0;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function verifyCoverageGate() {
|
|
32
|
+
const [major, minor] = process.versions.node.split('.').map(Number);
|
|
33
|
+
const eligible = (major > 22 || (major === 22 && minor >= 8));
|
|
34
|
+
if (!eligible) {
|
|
35
|
+
console.error(`⚠️ Skipping coverage gate: needs Node >= 22.8 (current: ${process.version})`);
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
return runNpmScript('test:coverage', '📈 Running coverage gate...');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function main() {
|
|
42
|
+
const pkg = loadPackageJson();
|
|
43
|
+
if (!pkg || !pkg.scripts) {
|
|
44
|
+
console.error('⚠️ Stop Verifier: no verifiable package.json found -- nothing checked.');
|
|
45
|
+
process.exit(0);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const scripts = pkg.scripts;
|
|
49
|
+
let ranAny = false;
|
|
50
|
+
let failed = false;
|
|
51
|
+
|
|
52
|
+
if (typeof scripts.validate === 'string') {
|
|
53
|
+
ranAny = true;
|
|
54
|
+
if (!runNpmScript('validate', '🔍 Running architecture validation...')) failed = true;
|
|
55
|
+
}
|
|
56
|
+
if (typeof scripts['test:coverage'] === 'string') {
|
|
57
|
+
ranAny = true;
|
|
58
|
+
if (!verifyCoverageGate()) failed = true;
|
|
59
|
+
}
|
|
60
|
+
if (typeof scripts.test === 'string') {
|
|
61
|
+
ranAny = true;
|
|
62
|
+
if (!runNpmScript('test', '🧪 Running test suite...')) failed = true;
|
|
63
|
+
}
|
|
64
|
+
if (typeof scripts.lint === 'string') {
|
|
65
|
+
ranAny = true;
|
|
66
|
+
if (!runNpmScript('lint', '🔎 Running lint...')) failed = true;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (!ranAny) {
|
|
70
|
+
console.error('⚠️ Stop Verifier: no verifiable scripts found in package.json.');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
process.exit(failed ? 1 : 0);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
main();
|
|
@@ -567,7 +567,14 @@ if [[ ! -f "${SMOKE_TEST}" ]]; then
|
|
|
567
567
|
# ==============================================================================
|
|
568
568
|
set -euo pipefail
|
|
569
569
|
|
|
570
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
571
|
+
|
|
570
572
|
echo "Running boundary smoke verification..."
|
|
573
|
+
if [[ -f "${ROOT}/.agents/scripts/boundary_guard.js" ]] && command -v node >/dev/null 2>&1; then
|
|
574
|
+
node "${ROOT}/.agents/scripts/boundary_guard.js" "${ROOT}/src"
|
|
575
|
+
else
|
|
576
|
+
echo "⚠️ Boundary guard skipped (node or guard script unavailable)."
|
|
577
|
+
fi
|
|
571
578
|
# Extend with project-specific runtime health checks (e.g. ping health endpoint, CLI --help)
|
|
572
579
|
echo "✅ Boundary smoke verification passed!"
|
|
573
580
|
EOF
|
|
@@ -576,3 +583,9 @@ fi
|
|
|
576
583
|
|
|
577
584
|
echo "--------------------------------------------------------------"
|
|
578
585
|
echo "✅ Topology '${TOPOLOGY}' scaffolded with strict YAGNI (0 speculative folders)!"
|
|
586
|
+
echo ""
|
|
587
|
+
echo "🛑 MANDATORY HANDOVER GATE (ADR-008):"
|
|
588
|
+
echo " Bootstrapping is COMPLETE. Zero application domain entities have been created."
|
|
589
|
+
echo " DO NOT fabricate domain models, entities, or features during lets-build."
|
|
590
|
+
echo " Next: invoke 'product-analyst' & 'relentless-questioner' to conduct formal"
|
|
591
|
+
echo " stakeholder domain analysis, INVEST story decomposition, and Gherkin criteria."
|
package/.gitignore
CHANGED
package/CONTRIBUTING.md
CHANGED
|
@@ -60,9 +60,9 @@ Before submitting any code changes, all 5 quality gates must pass with zero erro
|
|
|
60
60
|
npm run validate
|
|
61
61
|
```
|
|
62
62
|
Validates root harness parity, progressive disclosure rules, skill formats, markdown links, ADR ledger consistency, and toolchain gate presence.
|
|
63
|
-
6. **Vendored
|
|
63
|
+
6. **Vendored Engine Sync:** after any change under `src/agent-guard*.ts` or `src/boundaries.ts` and `npm run build`, re-copy the compiled engine files into `.agents/lib/` (byte-equality is enforced by `tests/agent-guard.test.ts` and `tests/boundary-guard.test.ts`):
|
|
64
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)"
|
|
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','boundaries.js'])fs.copyFileSync('lib/'+f,'.agents/lib/'+f)"
|
|
66
66
|
```
|
|
67
67
|
|
|
68
68
|
---
|
package/README.md
CHANGED
|
@@ -9,8 +9,6 @@
|
|
|
9
9
|
[](./LICENSE)
|
|
10
10
|
|
|
11
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.
|
|
12
|
-
[](https://www.npmjs.com/package/azcodr)
|
|
13
|
-
[](./LICENSE)
|
|
14
12
|
|
|
15
13
|
> **Production-oriented architecture governance and topology-scoped scaffolding for AI-assisted development (early release).**
|
|
16
14
|
> *AI agents move fast. Architecture drifts. Azcodr turns architectural intent into executable constraints your agent cannot skip.*
|
|
@@ -38,7 +36,7 @@
|
|
|
38
36
|
|
|
39
37
|
## ⚡ 60-Second Demo: See Drift Prevention in Action
|
|
40
38
|
|
|
41
|
-
Azcodr turns architectural philosophy into machine-checked constraints
|
|
39
|
+
Azcodr turns architectural philosophy into machine-checked constraints. In a fresh scaffold the runtime hooks ship **enabled by default**, and the boundary engine runs in CI, so drift is caught both while the agent edits and after it commits.
|
|
42
40
|
|
|
43
41
|
### 1. Standalone Architectural Check
|
|
44
42
|
Run the validator on any repository without scaffolding:
|
|
@@ -67,7 +65,7 @@ When an AI coding agent attempts to dump new logic into a file already exceeding
|
|
|
67
65
|
```
|
|
68
66
|
|
|
69
67
|
### 3. Zero-Dependency Boundary & Cycle Enforcement
|
|
70
|
-
Inspect dependency cycles and layer boundaries on demand without third-party linters:
|
|
68
|
+
Inspect dependency cycles and layer boundaries on demand without third-party linters. The same engine is vendored into every scaffolded project (`.agents/scripts/boundary_guard.js`) and runs in the generated CI:
|
|
71
69
|
```bash
|
|
72
70
|
npx azcodr boundaries [directory]
|
|
73
71
|
```
|
|
@@ -80,17 +78,17 @@ npx azcodr boundaries [directory]
|
|
|
80
78
|
Action Required: Invert dependencies via Ports or extract shared modules.
|
|
81
79
|
```
|
|
82
80
|
|
|
83
|
-
### 4.
|
|
84
|
-
|
|
81
|
+
### 4. Detector Validation on Synthetic Fixtures (Not a Live-Agent Trial)
|
|
82
|
+
The evaluator's ability to detect the 3 trap classes is exercised against **hand-written fixtures**, not live agent output ([`benchmark/RESULTS.md`](https://github.com/prosubodh/azcodr/blob/main/benchmark/RESULTS.md)):
|
|
85
83
|
|
|
86
84
|
| Metric | Arm A (Control) | Arm B (Hooks Only) | Arm C (Full Azcodr) |
|
|
87
85
|
|---|:---:|:---:|:---:|
|
|
88
86
|
| **Files >300 Lines** | 1 (365 lines in `auth.ts`) | **0** (Modularized) | **0** (Modularized) |
|
|
89
87
|
| **Circular Dependency Cycles** | 1 (`billing <-> tenant`) | 1 (`billing <-> tenant`) | **0** (Decoupled Port) |
|
|
90
88
|
| **Layer Boundary Breaches** | 1 (`domain -> infra`) | 1 (`domain -> infra`) | **0** (Hexagonal Port) |
|
|
91
|
-
| **
|
|
89
|
+
| **Detected drift-free** | ❌ FAILED | ❌ FAILED | ✅ **0 detector violations** |
|
|
92
90
|
|
|
93
|
-
Run the
|
|
91
|
+
These fixtures are committed by hand to prove the detector fires; they are **not evidence** that an AI agent drifts. Real-agent results (tokens, completion, review minutes) are planned and not yet measured. Run the detector yourself:
|
|
94
92
|
```bash
|
|
95
93
|
node benchmark/run-benchmark.js
|
|
96
94
|
```
|
|
@@ -128,6 +128,27 @@ names the test that locks it:
|
|
|
128
128
|
`npm run lint` fails until the pinned tool lands instead of echoing
|
|
129
129
|
success.
|
|
130
130
|
|
|
131
|
+
## Addendum (2026-10-09): boundaries shipped, starter CI generated, hooks on
|
|
132
|
+
|
|
133
|
+
6. **Boundary engine now ships in the scaffold.** `lib/boundaries.js` is
|
|
134
|
+
vendored into `.agents/lib/boundaries.js` (byte-locked by
|
|
135
|
+
`tests/boundary-guard.test.ts`, re-copied after every build per
|
|
136
|
+
`CONTRIBUTING.md`) and a vendored-first runner `.agents/scripts/boundary_guard.js`
|
|
137
|
+
exits 0/1 on a repo's cycle & layer verdict (exit 2 on a missing engine).
|
|
138
|
+
The starter `package.json` gains a `boundaries` script pointing at it.
|
|
139
|
+
7. **Generated CI is now green out of the box.** azcodr's own dev workflows
|
|
140
|
+
(`ci.yml`, `publish.yml`) call scripts a fresh project does not have, so
|
|
141
|
+
shipping them verbatim made a scaffolded repo's CI fail on every push. The
|
|
142
|
+
scaffolder now replaces them with a starter `.github/workflows/ci.yml` that
|
|
143
|
+
only needs Node, the vendored boundary guard, and `scripts/validate-cli.js`.
|
|
144
|
+
Locked by `tests/scaffold-ci.test.ts`.
|
|
145
|
+
8. **Hooks ship enabled.** `.agents/hooks.json` (+`.example`) now ships all four
|
|
146
|
+
hooks `enabled: true` (safety, architectural, post-tool-lint, stop-verifier),
|
|
147
|
+
amending ADR-020 which deliberately shipped them disabled. The trade-off is
|
|
148
|
+
recorded in the 2026-10-09 ADR: default-on means a harness that loads the
|
|
149
|
+
file blocks immediately, at the cost that lint/stop hooks can interrupt
|
|
150
|
+
worthless-but-harmless sessions in a bare scaffold.
|
|
151
|
+
|
|
131
152
|
## Reproduce
|
|
132
153
|
|
|
133
154
|
```bash
|
package/lib/scaffold.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../src/scaffold.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,KAAK,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACzE,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC9E,OAAO,EAAE,aAAa,EAAE,sBAAsB,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAC9F,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AACvD,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../src/scaffold.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,KAAK,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACzE,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC9E,OAAO,EAAE,aAAa,EAAE,sBAAsB,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAC9F,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AACvD,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAGhD,OAAO,EAAE,cAAc,EAAE,CAAC;AAE1B;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE,SAAS,MAAM,EAW3C,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,OAAO,CAAC;IACxB,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAmCD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,GAAE,qBAA0B,GAAG,IAAI,CAW3F;AA8FD;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAC1B,SAAS,EAAE,MAAM,EACjB,WAAW,GAAE,MAAyB,EACtC,OAAO,GAAE,mBAAwB,GAChC,MAAM,EAAE,CAeV;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,OAAO,GAAE,eAAoB,GAAG,cAAc,CActE;AAED,OAAO,EACL,aAAa,EAAE,sBAAsB,EAAE,yBAAyB,EAChE,qBAAqB,EAAE,mBAAmB,EAAE,OAAO,EAAE,MAAM,EAC3D,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,iBAAiB,EAC5D,CAAC;AAEF,YAAY,EAAE,oBAAoB,EAAE,cAAc,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;AAE5F,wBAKE"}
|
package/lib/scaffold.js
CHANGED
|
@@ -7,6 +7,7 @@ import { assertInside, isProtectedTarget, getTemplateDir } from './guards.js';
|
|
|
7
7
|
import { ensureSymlink, ensureSymlinkOrPointer, isSameCaseInsensitiveFile } from './links.js';
|
|
8
8
|
import { makeScriptsExecutable } from './permissions.js';
|
|
9
9
|
import { isInsideGitWorkTree, initGit } from './repo.js';
|
|
10
|
+
import { writeStarterCi } from './starter-ci.js';
|
|
10
11
|
export { getTemplateDir };
|
|
11
12
|
/**
|
|
12
13
|
* Canonical list of template files and directories copied into a new workspace.
|
|
@@ -131,6 +132,8 @@ function starterPackageJson(resolvedTarget) {
|
|
|
131
132
|
test: 'node --test',
|
|
132
133
|
'test:coverage': 'jest --coverage',
|
|
133
134
|
lint: 'echo "No linter configured yet. Run /lets-build to configure toolchain."',
|
|
135
|
+
boundaries: 'node .agents/scripts/boundary_guard.js',
|
|
136
|
+
smoke: 'node .agents/scripts/smoke_test.js',
|
|
134
137
|
validate: 'node scripts/validate-cli.js'
|
|
135
138
|
}
|
|
136
139
|
};
|
|
@@ -173,6 +176,7 @@ export function copyTemplate(targetDir, templateDir = getTemplateDir(), options
|
|
|
173
176
|
actions.push(...ensureHarnessParity(resolvedTarget, dryRun));
|
|
174
177
|
actions.push(...ensureCopilotParity(resolvedTarget, dryRun));
|
|
175
178
|
actions.push(...ensureStarterPackageJson(resolvedTarget, dryRun));
|
|
179
|
+
actions.push(...writeStarterCi(resolvedTarget, dryRun));
|
|
176
180
|
const inspectDir = dryRun ? resolvedTemplate : resolvedTarget;
|
|
177
181
|
actions.push(...chmodTemplateScripts(inspectDir, dryRun));
|
|
178
182
|
return actions;
|
package/lib/scaffold.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../src/scaffold.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,OAAO,MAAM,cAAc,CAAC;AACnC,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAEzD,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC9E,OAAO,EAAE,aAAa,EAAE,sBAAsB,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAE9F,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../src/scaffold.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* azcodr's own release/CI workflows. They target this repository's scripts
|
|
3
|
+
* (build, mutation, benchmark) and must never run inside a scaffolded project;
|
|
4
|
+
* `writeStarterCi` removes them from the target and installs a starter CI.
|
|
5
|
+
*/
|
|
6
|
+
export declare const AZCODR_DEV_WORKFLOWS: readonly string[];
|
|
7
|
+
/**
|
|
8
|
+
* Starter governance CI written into every scaffolded project. It depends only
|
|
9
|
+
* on Node + the vendored guard engine, so it is green out of the box (unlike
|
|
10
|
+
* azcodr's own dev workflows, which call scripts a fresh project does not have).
|
|
11
|
+
*/
|
|
12
|
+
export declare const STARTER_CI = "name: ci\n\non:\n push:\n branches: [main]\n pull_request:\n\npermissions:\n contents: read\n\njobs:\n governance:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - uses: actions/setup-node@v4\n with:\n node-version: 22.x\n - name: Architectural boundary & cycle check\n run: node .agents/scripts/boundary_guard.js\n - name: Governance validation\n run: node scripts/validate-cli.js\n";
|
|
13
|
+
/**
|
|
14
|
+
* Writes the starter governance CI into a scaffold target and removes azcodr's
|
|
15
|
+
* own dev workflows from it, so a generated project's CI is green out of the
|
|
16
|
+
* box instead of calling scripts a fresh project does not have.
|
|
17
|
+
*
|
|
18
|
+
* @param resolvedTarget - Absolute path to the scaffold target directory.
|
|
19
|
+
* @param dryRun - Record the action without touching the filesystem.
|
|
20
|
+
* @returns Recorded scaffolding action descriptions.
|
|
21
|
+
*/
|
|
22
|
+
export declare function writeStarterCi(resolvedTarget: string, dryRun: boolean): string[];
|
|
23
|
+
declare const _default: {
|
|
24
|
+
STARTER_CI: string;
|
|
25
|
+
AZCODR_DEV_WORKFLOWS: readonly string[];
|
|
26
|
+
writeStarterCi: typeof writeStarterCi;
|
|
27
|
+
};
|
|
28
|
+
export default _default;
|
|
29
|
+
//# sourceMappingURL=starter-ci.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"starter-ci.d.ts","sourceRoot":"","sources":["../src/starter-ci.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,EAAE,SAAS,MAAM,EAA8B,CAAC;AAEjF;;;;GAIG;AACH,eAAO,MAAM,UAAU,sdAsBtB,CAAC;AAEF;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,cAAc,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,MAAM,EAAE,CAUhF;;;;;;AAED,wBAAoE"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
/**
|
|
4
|
+
* azcodr's own release/CI workflows. They target this repository's scripts
|
|
5
|
+
* (build, mutation, benchmark) and must never run inside a scaffolded project;
|
|
6
|
+
* `writeStarterCi` removes them from the target and installs a starter CI.
|
|
7
|
+
*/
|
|
8
|
+
export const AZCODR_DEV_WORKFLOWS = ['ci.yml', 'publish.yml'];
|
|
9
|
+
/**
|
|
10
|
+
* Starter governance CI written into every scaffolded project. It depends only
|
|
11
|
+
* on Node + the vendored guard engine, so it is green out of the box (unlike
|
|
12
|
+
* azcodr's own dev workflows, which call scripts a fresh project does not have).
|
|
13
|
+
*/
|
|
14
|
+
export const STARTER_CI = `name: ci
|
|
15
|
+
|
|
16
|
+
on:
|
|
17
|
+
push:
|
|
18
|
+
branches: [main]
|
|
19
|
+
pull_request:
|
|
20
|
+
|
|
21
|
+
permissions:
|
|
22
|
+
contents: read
|
|
23
|
+
|
|
24
|
+
jobs:
|
|
25
|
+
governance:
|
|
26
|
+
runs-on: ubuntu-latest
|
|
27
|
+
steps:
|
|
28
|
+
- uses: actions/checkout@v4
|
|
29
|
+
- uses: actions/setup-node@v4
|
|
30
|
+
with:
|
|
31
|
+
node-version: 22.x
|
|
32
|
+
- name: Architectural boundary & cycle check
|
|
33
|
+
run: node .agents/scripts/boundary_guard.js
|
|
34
|
+
- name: Governance validation
|
|
35
|
+
run: node scripts/validate-cli.js
|
|
36
|
+
`;
|
|
37
|
+
/**
|
|
38
|
+
* Writes the starter governance CI into a scaffold target and removes azcodr's
|
|
39
|
+
* own dev workflows from it, so a generated project's CI is green out of the
|
|
40
|
+
* box instead of calling scripts a fresh project does not have.
|
|
41
|
+
*
|
|
42
|
+
* @param resolvedTarget - Absolute path to the scaffold target directory.
|
|
43
|
+
* @param dryRun - Record the action without touching the filesystem.
|
|
44
|
+
* @returns Recorded scaffolding action descriptions.
|
|
45
|
+
*/
|
|
46
|
+
export function writeStarterCi(resolvedTarget, dryRun) {
|
|
47
|
+
const actions = ['generate: .github/workflows/ci.yml (starter governance CI)'];
|
|
48
|
+
if (dryRun)
|
|
49
|
+
return actions;
|
|
50
|
+
const workflowsDir = path.join(resolvedTarget, '.github', 'workflows');
|
|
51
|
+
fs.mkdirSync(workflowsDir, { recursive: true });
|
|
52
|
+
for (const name of AZCODR_DEV_WORKFLOWS) {
|
|
53
|
+
fs.rmSync(path.join(workflowsDir, name), { force: true });
|
|
54
|
+
}
|
|
55
|
+
fs.writeFileSync(path.join(workflowsDir, 'ci.yml'), STARTER_CI, 'utf-8');
|
|
56
|
+
return actions;
|
|
57
|
+
}
|
|
58
|
+
export default { STARTER_CI, AZCODR_DEV_WORKFLOWS, writeStarterCi };
|
|
59
|
+
//# sourceMappingURL=starter-ci.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"starter-ci.js","sourceRoot":"","sources":["../src/starter-ci.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B;;;;GAIG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAsB,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC;AAEjF;;;;GAIG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG;;;;;;;;;;;;;;;;;;;;;;CAsBzB,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAAC,cAAsB,EAAE,MAAe;IACpE,MAAM,OAAO,GAAG,CAAC,4DAA4D,CAAC,CAAC;IAC/E,IAAI,MAAM;QAAE,OAAO,OAAO,CAAC;IAC3B,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,SAAS,EAAE,WAAW,CAAC,CAAC;IACvE,EAAE,CAAC,SAAS,CAAC,YAAY,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAChD,KAAK,MAAM,IAAI,IAAI,oBAAoB,EAAE,CAAC;QACxC,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5D,CAAC;IACD,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,QAAQ,CAAC,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC;IACzE,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,eAAe,EAAE,UAAU,EAAE,oBAAoB,EAAE,cAAc,EAAE,CAAC"}
|
package/memory.md
CHANGED
|
@@ -37,6 +37,8 @@
|
|
|
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
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
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) |
|
|
40
|
+
| [ADR-021](./memory.md#adr-021-default-on-guards-boundaries-and-starter-ci-ship-in-the-scaffold) | Default-on guards; boundaries & starter CI ship in the scaffold | 2026-10-09 | ACCEPTED | [`clean_code.md`](./docs/rules/clean_code.md) |
|
|
41
|
+
| [ADR-022](./memory.md#adr-022-cross-platform-hook-resolution-scaffolding-handover-gate-desktop-doctor--smoke-standard) | Cross-platform hook resolution, scaffolding handover gate, desktop doctor & smoke standard | 2026-10-10 | ACCEPTED | [`agentic_configuration.md`](./docs/rules/agentic_configuration.md) |
|
|
40
42
|
|
|
41
43
|
---
|
|
42
44
|
|
|
@@ -133,6 +135,20 @@
|
|
|
133
135
|
- **Consequences:** ✅ The catastrophic `--force` paths are closed, locked by `tests/protected-target.test.js` (14 tests, including symlink-alias and fault-injection cases for the two defensive catches). ✅ Only the exact protected directories are blocked: `~/my-project` and any normal subdirectory still scaffold. ✅ New stable error code follows the existing contract (branch on `code`, never message). ⚠️ A user who genuinely wants a project AT `~` must pick a subdirectory; that friction is the point.
|
|
134
136
|
- **Enforced In:** `lib/scaffold.js` (`isProtectedTarget`, `validateTarget`), `bin/azcodr.js` (fail-fast), `lib/index.d.ts`, `tests/protected-target.test.js`, `tests/error-codes.test.js`, `README.md` (security notes)
|
|
135
137
|
|
|
138
|
+
#### ADR-022: Cross-Platform Hook Resolution, Scaffolding Handover Gate, Desktop Doctor & Smoke Standard
|
|
139
|
+
- **Date:** 2026-10-10 | **Status:** ACCEPTED
|
|
140
|
+
- **Context:** Retrospectives across `thebrowser` and `sthanori` revealed recurring operational and architectural friction: (1) Running on Windows hosts under Antigravity/cmd.exe, hook commands specified as POSIX paths (`./.agents/...`) failed with `'.' is not recognized as an internal or external command`, and Antigravity sets CWD to `.agents/`, breaking relative paths. (2) During scaffolding, eager agents fabricated domain entities without user discovery, violating the bootstrapping boundary (ISSUE-005). (3) Hybrid desktop builds suffered silent defects (missing `custom-protocol`, hollow <5KB CSS bundles due to missing PostCSS, and background WebView2 process lock crashes `0x800700AA`). (4) In-memory unit test suites gave false confidence while live reverse proxies, TCP sockets, and Content-Types failed (ISSUE-004), alongside local dev port collisions (port 5173 RPMS collision in `thebrowser`).
|
|
141
|
+
- **Decision:** (1) Standardize all lifecycle hook commands in `.agents/hooks.json` (+`.example`) on cross-platform `node` runtime invocations (`node .agents/scripts/...`), supported by forwarders resolving seamlessly whether CWD is workspace root or `.agents/`. (2) Ship pure Node.js implementations for `safety_guard.js` and `verify_completion.js`, eliminating bash dependencies on Windows. (3) Enforce the mandatory Phase 5 Handover Gate in `bootstrap_workspace.sh` and `/lets-build`, halting code generation after technical plumbing and `/healthz`. (4) Author `.agents/scripts/desktop_doctor.js` (verifying `Cargo.toml` Tauri `custom-protocol` and CSS bundle density ≥ 5KB) and `.agents/scripts/smoke_test.js` (boundary graph check and port collision doctoring). Wire `smoke` into starter `package.json`.
|
|
142
|
+
- **Consequences:** ✅ Zero Windows cmd shell syntax errors during PreToolUse/PostToolUse/Stop hook execution. ✅ Full cross-platform test and verification parity across Windows, macOS, and Linux. ✅ Deterministic prevention of premature domain scaffolding, hybrid desktop build defects, and silent port collisions. ✅ 100.00% test coverage and Clean Code fitness functions preserved across all 49 test suites and 560 tests.
|
|
143
|
+
- **Enforced In:** `.agents/hooks.json`, `.agents/hooks.json.example`, `.agents/scripts/safety_guard.js`, `.agents/scripts/verify_completion.js`, `.agents/scripts/desktop_doctor.js`, `.agents/scripts/smoke_test.js`, `.agents/skills/lets-build/scripts/bootstrap_workspace.sh`, `src/scaffold.ts`, `tests/hooks-default.test.ts`, `tests/desktop-doctor.test.ts`, `tests/smoke-test.test.ts`, `tests/bootstrap-toolchain.test.ts`
|
|
144
|
+
|
|
145
|
+
#### ADR-021: Default-On Guards; Boundaries & Starter CI Ship in the Scaffold
|
|
146
|
+
- **Date:** 2026-10-09 | **Status:** ACCEPTED
|
|
147
|
+
- **Context:** The 2026-10-08 consolidated external review left three open severity items: (1) the benchmark read as a live-agent trial while its fixtures were synthetic ("100% drift elimination" over fixtures no agent produced); (2) "your agent cannot skip" was false because every shipped hook in `.agents/hooks.json` was `enabled: false`; (3) the boundary engine — the component that stopped the cycle and the layer breach in the results — was not wired into generated projects or their CI. A further finding from the review's follow-up audit: scaffolded projects inherited azcodr's own `ci.yml`/`publish.yml`, which call scripts (build, mutation, benchmark) a fresh project does not have, so generated CI failed on every push out of the box.
|
|
148
|
+
- **Decision:** (1) Retitle the benchmark as detector validation on synthetic fixtures, pre-register the real-agent trial in `benchmark/REAL-AGENT-RUNBOOK.md` with a result template, and stop narrating "the agent" in `benchmark/RESULTS.md`. (2) Ship all four hooks with `enabled: true` in `.agents/hooks.json` (+`.example`), amending ADR-020 — default-off contradicts the tagline; the cost that lint/stop hooks can interrupt a bare scaffold is accepted and recorded. (3) Vendor `lib/boundaries.js` into `.agents/lib/boundaries.js` (byte-locked by test), add a vendored-first runner `.agents/scripts/boundary_guard.js` (exit 0/1 verdict, exit 2 on missing engine), point the starter `package.json` `boundaries` script at it, and make `bootstrap_workspace.sh`'s smoke test execute it. (4) The scaffolder writes a starter `.github/workflows/ci.yml` (boundary guard + `validate-cli.js`; Node only) and removes azcodr's dev-only workflows from the target, so a scaffolded repo's CI is green out of the box. (5) Remove the personal author email from `package.json` (role-scoped `+security`/`+conduct` addresses stay: they are functional routing, not identity).
|
|
149
|
+
- **Consequences:** ✅ Enforcement is on by default where the harness loads the file; boundary checks run in generated CI. ✅ Generated CI no longer fails because of azcodr's own scripts. ✅ The benchmark headline no longer overclaims. ⚠️ Default-on hooks can interrupt a harnessed session in a bare scaffold; ⚠️ live-agent drift-reduction evidence is still unmeasured; ⚠️ azcodr's dev workflows no longer propagate into user projects.
|
|
150
|
+
- **Enforced In:** `.agents/hooks.json`, `.agents/hooks.json.example`, `.agents/scripts/boundary_guard.js`, `.agents/lib/boundaries.js`, `src/scaffold.ts`, `src/starter-ci.ts`, `.agents/skills/lets-build/scripts/bootstrap_workspace.sh`, `benchmark/RESULTS.md`, `benchmark/REAL-AGENT-RUNBOOK.md`, `package.json`, `tests/boundary-guard.test.ts`, `tests/scaffold-ci.test.ts`, `tests/hooks-default.test.ts`
|
|
151
|
+
|
|
136
152
|
#### ADR-009: memory.md Is Append-Only; the Scaffolder Never Rewrites the Ledger
|
|
137
153
|
- **Date:** 2026-10-07 | **Status:** ACCEPTED
|
|
138
154
|
- **Context:** `.agents/skills/lets-build/scripts/bootstrap_workspace.sh` overwrote `memory.md` with a heredoc whenever the file contained any ADR heading that was not the untouched "No decisions recorded yet" placeholder. Reproduced directly: a project whose ADR read "we will never drop SQLite support" was silently replaced with an empty template after one `/lets-build` run — no error, no backup. It also fired on *high* numbering, so a mature project's ledger was equally destroyed. The intent was to strip template ADRs leaked from this repo; the trigger was the presence of real ones. ADRs are immutable history by rule (`docs/rules/agentic_configuration.md`), so a scaffolder rewriting them was a category error. Two further determinism defects in the same script: Topologies D (Canvas Game) and F (Systems Library) were documented in `SKILL.md` but had no `case` arm, so they fell through to a generic tree and **exited 0** — a wrong-but-successful scaffold the agent would report as done; and the `$LANGUAGE` argument was echoed and never used, so every language produced an identical tree while the skill claimed determinism.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "azcodr",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "Architecture governance toolkit with
|
|
3
|
+
"version": "2.7.0",
|
|
4
|
+
"description": "Architecture governance toolkit, with an optional starter (early release)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"azcodr": "bin/azcodr.js"
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"agents",
|
|
45
45
|
"npx"
|
|
46
46
|
],
|
|
47
|
-
"author": "Subodh Khanal
|
|
47
|
+
"author": "Subodh Khanal",
|
|
48
48
|
"license": "MIT",
|
|
49
49
|
"repository": {
|
|
50
50
|
"type": "git",
|
package/src/scaffold.ts
CHANGED
|
@@ -10,6 +10,7 @@ import type { EnsureSymlinkOptions } from './links.js';
|
|
|
10
10
|
import { makeScriptsExecutable } from './permissions.js';
|
|
11
11
|
import { isInsideGitWorkTree, initGit } from './repo.js';
|
|
12
12
|
import type { InitGitOptions } from './repo.js';
|
|
13
|
+
import { writeStarterCi } from './starter-ci.js';
|
|
13
14
|
|
|
14
15
|
export { getTemplateDir };
|
|
15
16
|
|
|
@@ -189,6 +190,8 @@ function starterPackageJson(resolvedTarget: string): Record<string, unknown> {
|
|
|
189
190
|
test: 'node --test',
|
|
190
191
|
'test:coverage': 'jest --coverage',
|
|
191
192
|
lint: 'echo "No linter configured yet. Run /lets-build to configure toolchain."',
|
|
193
|
+
boundaries: 'node .agents/scripts/boundary_guard.js',
|
|
194
|
+
smoke: 'node .agents/scripts/smoke_test.js',
|
|
192
195
|
validate: 'node scripts/validate-cli.js'
|
|
193
196
|
}
|
|
194
197
|
};
|
|
@@ -236,6 +239,7 @@ export function copyTemplate(
|
|
|
236
239
|
actions.push(...ensureHarnessParity(resolvedTarget, dryRun));
|
|
237
240
|
actions.push(...ensureCopilotParity(resolvedTarget, dryRun));
|
|
238
241
|
actions.push(...ensureStarterPackageJson(resolvedTarget, dryRun));
|
|
242
|
+
actions.push(...writeStarterCi(resolvedTarget, dryRun));
|
|
239
243
|
const inspectDir = dryRun ? resolvedTemplate : resolvedTarget;
|
|
240
244
|
actions.push(...chmodTemplateScripts(inspectDir, dryRun));
|
|
241
245
|
return actions;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* azcodr's own release/CI workflows. They target this repository's scripts
|
|
6
|
+
* (build, mutation, benchmark) and must never run inside a scaffolded project;
|
|
7
|
+
* `writeStarterCi` removes them from the target and installs a starter CI.
|
|
8
|
+
*/
|
|
9
|
+
export const AZCODR_DEV_WORKFLOWS: readonly string[] = ['ci.yml', 'publish.yml'];
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Starter governance CI written into every scaffolded project. It depends only
|
|
13
|
+
* on Node + the vendored guard engine, so it is green out of the box (unlike
|
|
14
|
+
* azcodr's own dev workflows, which call scripts a fresh project does not have).
|
|
15
|
+
*/
|
|
16
|
+
export const STARTER_CI = `name: ci
|
|
17
|
+
|
|
18
|
+
on:
|
|
19
|
+
push:
|
|
20
|
+
branches: [main]
|
|
21
|
+
pull_request:
|
|
22
|
+
|
|
23
|
+
permissions:
|
|
24
|
+
contents: read
|
|
25
|
+
|
|
26
|
+
jobs:
|
|
27
|
+
governance:
|
|
28
|
+
runs-on: ubuntu-latest
|
|
29
|
+
steps:
|
|
30
|
+
- uses: actions/checkout@v4
|
|
31
|
+
- uses: actions/setup-node@v4
|
|
32
|
+
with:
|
|
33
|
+
node-version: 22.x
|
|
34
|
+
- name: Architectural boundary & cycle check
|
|
35
|
+
run: node .agents/scripts/boundary_guard.js
|
|
36
|
+
- name: Governance validation
|
|
37
|
+
run: node scripts/validate-cli.js
|
|
38
|
+
`;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Writes the starter governance CI into a scaffold target and removes azcodr's
|
|
42
|
+
* own dev workflows from it, so a generated project's CI is green out of the
|
|
43
|
+
* box instead of calling scripts a fresh project does not have.
|
|
44
|
+
*
|
|
45
|
+
* @param resolvedTarget - Absolute path to the scaffold target directory.
|
|
46
|
+
* @param dryRun - Record the action without touching the filesystem.
|
|
47
|
+
* @returns Recorded scaffolding action descriptions.
|
|
48
|
+
*/
|
|
49
|
+
export function writeStarterCi(resolvedTarget: string, dryRun: boolean): string[] {
|
|
50
|
+
const actions = ['generate: .github/workflows/ci.yml (starter governance CI)'];
|
|
51
|
+
if (dryRun) return actions;
|
|
52
|
+
const workflowsDir = path.join(resolvedTarget, '.github', 'workflows');
|
|
53
|
+
fs.mkdirSync(workflowsDir, { recursive: true });
|
|
54
|
+
for (const name of AZCODR_DEV_WORKFLOWS) {
|
|
55
|
+
fs.rmSync(path.join(workflowsDir, name), { force: true });
|
|
56
|
+
}
|
|
57
|
+
fs.writeFileSync(path.join(workflowsDir, 'ci.yml'), STARTER_CI, 'utf-8');
|
|
58
|
+
return actions;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export default { STARTER_CI, AZCODR_DEV_WORKFLOWS, writeStarterCi };
|