azcodr 2.6.0 → 2.7.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.
Files changed (64) hide show
  1. package/.agents/hooks.json +3 -3
  2. package/.agents/hooks.json.example +4 -4
  3. package/.agents/lib/agent-guard-command.js +6 -0
  4. package/.agents/lib/agent-guard-file.js +15 -0
  5. package/.agents/lib/agent-guard-tdd.js +30 -0
  6. package/.agents/lib/boundaries.js +35 -0
  7. package/.agents/scripts/desktop_doctor.js +108 -0
  8. package/.agents/scripts/safety_guard.js +82 -0
  9. package/.agents/scripts/smoke_test.js +78 -0
  10. package/.agents/scripts/verify_completion.js +76 -0
  11. package/.agents/skills/lets-build/scripts/bootstrap_workspace.sh +6 -0
  12. package/.gitignore +1 -0
  13. package/lib/agent-guard-command.d.ts +17 -0
  14. package/lib/agent-guard-command.d.ts.map +1 -1
  15. package/lib/agent-guard-command.js +6 -0
  16. package/lib/agent-guard-command.js.map +1 -1
  17. package/lib/agent-guard-file.d.ts +31 -0
  18. package/lib/agent-guard-file.d.ts.map +1 -1
  19. package/lib/agent-guard-file.js +15 -0
  20. package/lib/agent-guard-file.js.map +1 -1
  21. package/lib/agent-guard-tdd.d.ts +42 -0
  22. package/lib/agent-guard-tdd.d.ts.map +1 -1
  23. package/lib/agent-guard-tdd.js +30 -0
  24. package/lib/agent-guard-tdd.js.map +1 -1
  25. package/lib/agent-guard.d.ts +17 -0
  26. package/lib/agent-guard.d.ts.map +1 -1
  27. package/lib/agent-guard.js.map +1 -1
  28. package/lib/boundaries.d.ts +54 -0
  29. package/lib/boundaries.d.ts.map +1 -1
  30. package/lib/boundaries.js +35 -0
  31. package/lib/boundaries.js.map +1 -1
  32. package/lib/errors.d.ts +16 -0
  33. package/lib/errors.d.ts.map +1 -1
  34. package/lib/errors.js +8 -0
  35. package/lib/errors.js.map +1 -1
  36. package/lib/guards.d.ts +4 -0
  37. package/lib/guards.d.ts.map +1 -1
  38. package/lib/guards.js.map +1 -1
  39. package/lib/links.d.ts +4 -0
  40. package/lib/links.d.ts.map +1 -1
  41. package/lib/links.js.map +1 -1
  42. package/lib/repo.d.ts +2 -0
  43. package/lib/repo.d.ts.map +1 -1
  44. package/lib/repo.js.map +1 -1
  45. package/lib/scaffold.d.ts +19 -0
  46. package/lib/scaffold.d.ts.map +1 -1
  47. package/lib/scaffold.js +4 -0
  48. package/lib/scaffold.js.map +1 -1
  49. package/lib/validate.d.ts +17 -0
  50. package/lib/validate.d.ts.map +1 -1
  51. package/lib/validate.js.map +1 -1
  52. package/memory.md +8 -0
  53. package/package.json +1 -1
  54. package/src/agent-guard-command.ts +17 -0
  55. package/src/agent-guard-file.ts +31 -0
  56. package/src/agent-guard-tdd.ts +42 -0
  57. package/src/agent-guard.ts +17 -0
  58. package/src/boundaries.ts +54 -0
  59. package/src/errors.ts +10 -0
  60. package/src/guards.ts +4 -0
  61. package/src/links.ts +4 -0
  62. package/src/repo.ts +2 -0
  63. package/src/scaffold.ts +22 -7
  64. package/src/validate.ts +17 -0
@@ -7,7 +7,7 @@
7
7
  "hooks": [
8
8
  {
9
9
  "type": "command",
10
- "command": "./.agents/scripts/safety_guard.sh",
10
+ "command": "node .agents/scripts/safety_guard.js",
11
11
  "timeout": 15
12
12
  }
13
13
  ]
@@ -22,7 +22,7 @@
22
22
  "hooks": [
23
23
  {
24
24
  "type": "command",
25
- "command": "./.agents/scripts/agent_guard.js",
25
+ "command": "node .agents/scripts/agent_guard.js",
26
26
  "timeout": 15
27
27
  }
28
28
  ]
@@ -49,7 +49,7 @@
49
49
  "Stop": [
50
50
  {
51
51
  "type": "command",
52
- "command": "./.agents/scripts/verify_completion.sh",
52
+ "command": "node .agents/scripts/verify_completion.js",
53
53
  "timeout": 120
54
54
  }
55
55
  ]
@@ -7,7 +7,7 @@
7
7
  "hooks": [
8
8
  {
9
9
  "type": "command",
10
- "command": "./.agents/scripts/safety_guard.sh",
10
+ "command": "node .agents/scripts/safety_guard.js",
11
11
  "timeout": 15
12
12
  }
13
13
  ]
@@ -22,7 +22,7 @@
22
22
  "hooks": [
23
23
  {
24
24
  "type": "command",
25
- "command": "./.agents/scripts/agent_guard.js",
25
+ "command": "node .agents/scripts/agent_guard.js",
26
26
  "timeout": 15
27
27
  }
28
28
  ]
@@ -49,8 +49,8 @@
49
49
  "Stop": [
50
50
  {
51
51
  "type": "command",
52
- "command": "./.agents/scripts/verify_completion.sh",
53
- "timeout": 15
52
+ "command": "node .agents/scripts/verify_completion.js",
53
+ "timeout": 120
54
54
  }
55
55
  ]
56
56
  }
@@ -75,6 +75,12 @@ export const DESTRUCTIVE_RULES = [
75
75
  reason: 'package publish must run via release workflow, not ad-hoc tool call'
76
76
  }
77
77
  ];
78
+ /**
79
+ * Collapses multi-line strings and extra whitespace into a single normalized shell command string.
80
+ *
81
+ * @param raw - Raw command string.
82
+ * @returns Cleaned single-line command string.
83
+ */
78
84
  export function normalizeCommand(raw) {
79
85
  return raw.replace(/[\r\n\t]+/g, ' ').replace(/\s+/g, ' ').trim();
80
86
  }
@@ -1,11 +1,26 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
+ /**
4
+ * Default maximum lines allowed per file before triggering Refactor-Before-Add enforcement.
5
+ */
3
6
  export const DEFAULT_MAX_FILE_LINES = 300;
7
+ /**
8
+ * Calculates the number of newline-delimited lines in a given text string.
9
+ *
10
+ * @param text - Input string to evaluate.
11
+ * @returns Total count of lines.
12
+ */
4
13
  export function countLines(text) {
5
14
  if (!text)
6
15
  return 0;
7
16
  return text.split('\n').length;
8
17
  }
18
+ /**
19
+ * Reads an existing file from disk and returns its current line count.
20
+ *
21
+ * @param filePath - Path of the file to inspect.
22
+ * @returns Total number of lines, or 0 if unreadable or non-existent.
23
+ */
9
24
  export function readExistingLines(filePath) {
10
25
  if (!fs.existsSync(filePath))
11
26
  return 0;
@@ -1,5 +1,11 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
+ /**
4
+ * Identifies whether a given path is a unit or integration test file.
5
+ *
6
+ * @param filePath - File path to inspect.
7
+ * @returns True if path matches test directory or file extension patterns.
8
+ */
3
9
  export function isTestFile(filePath) {
4
10
  const norm = filePath.replace(/\\/g, '/').toLowerCase();
5
11
  if (/(^|\/)(tests?|__tests__)(\/|$)/i.test(norm)) {
@@ -7,11 +13,23 @@ export function isTestFile(filePath) {
7
13
  }
8
14
  return /\.(test|spec)\.[a-z0-9]+$/i.test(norm);
9
15
  }
16
+ /**
17
+ * Identifies whether a given path is a non-code file (markdown, json, yaml, images).
18
+ *
19
+ * @param filePath - File path to inspect.
20
+ * @returns True if file extension indicates non-code asset or configuration.
21
+ */
10
22
  export function isNonCodeFile(filePath) {
11
23
  const ext = path.extname(filePath).toLowerCase();
12
24
  const nonCodeExts = new Set(['.md', '.json', '.yml', '.yaml', '.txt', '.toml', '.lock', '.svg', '.png']);
13
25
  return nonCodeExts.has(ext);
14
26
  }
27
+ /**
28
+ * Identifies whether a given file path corresponds to production code under src/, lib/, or app/.
29
+ *
30
+ * @param filePath - File path to inspect.
31
+ * @returns True if path is production source code subject to RED-before-GREEN discipline.
32
+ */
15
33
  export function isProductionFile(filePath) {
16
34
  if (isNonCodeFile(filePath))
17
35
  return false;
@@ -20,6 +38,12 @@ export function isProductionFile(filePath) {
20
38
  const norm = filePath.replace(/\\/g, '/').toLowerCase();
21
39
  return norm.includes('src/') || norm.includes('lib/') || norm.includes('app/');
22
40
  }
41
+ /**
42
+ * Loads session TDD tracking state from disk, defaulting to unrecorded state if missing.
43
+ *
44
+ * @param stateFile - Path to the persistent session state JSON file.
45
+ * @returns Parsed SessionTddState object.
46
+ */
23
47
  export function readTddState(stateFile) {
24
48
  if (!fs.existsSync(stateFile)) {
25
49
  return { lastFailingTestRecorded: false };
@@ -32,6 +56,12 @@ export function readTddState(stateFile) {
32
56
  return { lastFailingTestRecorded: false };
33
57
  }
34
58
  }
59
+ /**
60
+ * Persists session TDD state to disk with fail-safe error handling to avoid disrupting execution.
61
+ *
62
+ * @param stateFile - Path to the persistent session state JSON file.
63
+ * @param state - SessionTddState object to record.
64
+ */
35
65
  export function writeTddState(stateFile, state) {
36
66
  try {
37
67
  const dir = path.dirname(stateFile);
@@ -1,5 +1,8 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
+ /**
4
+ * Canonical architectural boundary rules enforcing Hexagonal / Clean Architecture layer constraints.
5
+ */
3
6
  export const DEFAULT_BOUNDARY_RULES = [
4
7
  {
5
8
  fromLayer: 'domain',
@@ -19,6 +22,13 @@ function isEligibleSource(name) {
19
22
  return false;
20
23
  return !name.includes('.test.') && !name.includes('.spec.');
21
24
  }
25
+ /**
26
+ * Recursively discovers all eligible source files (.ts and .js) within a directory,
27
+ * ignoring skipped directories (e.g. node_modules, lib) and test files.
28
+ *
29
+ * @param dir - Root directory to search.
30
+ * @returns Sorted array of absolute file paths.
31
+ */
22
32
  export function findSourceFiles(dir) {
23
33
  if (!fs.existsSync(dir))
24
34
  return [];
@@ -49,6 +59,12 @@ function resolveImportTarget(sourceFile, specifier) {
49
59
  }
50
60
  return null;
51
61
  }
62
+ /**
63
+ * Scans a source file and extracts all local relative import and export paths.
64
+ *
65
+ * @param filePath - Absolute or relative path to the source file.
66
+ * @returns Sorted array of resolved local file paths imported or re-exported.
67
+ */
52
68
  export function extractLocalImports(filePath) {
53
69
  if (!fs.existsSync(filePath))
54
70
  return [];
@@ -66,6 +82,12 @@ export function extractLocalImports(filePath) {
66
82
  }
67
83
  return imports.sort();
68
84
  }
85
+ /**
86
+ * Constructs a dependency graph mapping each source file to its imported local files.
87
+ *
88
+ * @param files - Array of source file paths.
89
+ * @returns Map where keys are source file paths and values are arrays of imported local file paths.
90
+ */
69
91
  export function buildDependencyGraph(files) {
70
92
  const graph = new Map();
71
93
  for (const file of files) {
@@ -73,6 +95,12 @@ export function buildDependencyGraph(files) {
73
95
  }
74
96
  return graph;
75
97
  }
98
+ /**
99
+ * Analyzes a dependency graph using depth-first search to detect circular import cycles.
100
+ *
101
+ * @param graph - Map of file paths to their imported dependency file paths.
102
+ * @returns Array of cycle paths, where each cycle path is an array of file paths.
103
+ */
76
104
  export function detectDependencyCycles(graph) {
77
105
  const cycles = [];
78
106
  const visited = new Set();
@@ -105,6 +133,13 @@ export function detectDependencyCycles(graph) {
105
133
  function hasLayerSegment(pathStr, segment) {
106
134
  return new RegExp(`(^|[/\\\\])${segment}([/\\\\]|$)`, 'i').test(pathStr);
107
135
  }
136
+ /**
137
+ * Evaluates a dependency graph against architectural boundary rules to detect prohibited cross-layer imports.
138
+ *
139
+ * @param graph - Map of file paths to their imported dependency file paths.
140
+ * @param rules - Architectural boundary rules to enforce (defaults to DEFAULT_BOUNDARY_RULES).
141
+ * @returns Array of detected boundary violations.
142
+ */
108
143
  export function detectBoundaryViolations(graph, rules = DEFAULT_BOUNDARY_RULES) {
109
144
  const violations = [];
110
145
  for (const [file, imports] of graph.entries()) {
@@ -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();
@@ -583,3 +583,9 @@ fi
583
583
 
584
584
  echo "--------------------------------------------------------------"
585
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
@@ -35,6 +35,7 @@ Thumbs.db
35
35
  .tmp/
36
36
  .tmp-scratch/
37
37
  opencode.json
38
+ .agents/.agents/
38
39
 
39
40
  # Case-insensitive filesystem parity (agents.md is generated/symlinked on Linux, native on macOS/Windows)
40
41
  agents.md
@@ -1,16 +1,33 @@
1
1
  /**
2
2
  * Command safety guard inspecting shell commands against a destructive denylist.
3
3
  */
4
+ /**
5
+ * Represents the result of evaluating a shell command against the safety denylist.
6
+ */
4
7
  export interface CommandViolation {
8
+ /** Whether the command is prohibited by the safety denylist. */
5
9
  blocked: boolean;
10
+ /** Explanatory rationale if the command is blocked. */
6
11
  reason?: string;
12
+ /** Normalized command string evaluated. */
7
13
  command?: string;
8
14
  }
15
+ /**
16
+ * Denylist pattern definition matching prohibited shell commands.
17
+ */
9
18
  export interface DenylistRule {
19
+ /** Regular expression pattern detecting prohibited command syntax. */
10
20
  pattern: RegExp;
21
+ /** Human-readable explanation of why the command is unsafe. */
11
22
  reason: string;
12
23
  }
13
24
  export declare const DESTRUCTIVE_RULES: readonly DenylistRule[];
25
+ /**
26
+ * Collapses multi-line strings and extra whitespace into a single normalized shell command string.
27
+ *
28
+ * @param raw - Raw command string.
29
+ * @returns Cleaned single-line command string.
30
+ */
14
31
  export declare function normalizeCommand(raw: string): string;
15
32
  /**
16
33
  * Checks a command string against the safety denylist.
@@ -1 +1 @@
1
- {"version":3,"file":"agent-guard-command.d.ts","sourceRoot":"","sources":["../src/agent-guard-command.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,eAAO,MAAM,iBAAiB,EAAE,SAAS,YAAY,EAyEpD,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,gBAAgB,CAehE;;;;;;AAED,wBAAuE"}
1
+ {"version":3,"file":"agent-guard-command.d.ts","sourceRoot":"","sources":["../src/agent-guard-command.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,gEAAgE;IAChE,OAAO,EAAE,OAAO,CAAC;IACjB,uDAAuD;IACvD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2CAA2C;IAC3C,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,sEAAsE;IACtE,OAAO,EAAE,MAAM,CAAC;IAChB,+DAA+D;IAC/D,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,eAAO,MAAM,iBAAiB,EAAE,SAAS,YAAY,EAyEpD,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,gBAAgB,CAehE;;;;;;AAED,wBAAuE"}
@@ -75,6 +75,12 @@ export const DESTRUCTIVE_RULES = [
75
75
  reason: 'package publish must run via release workflow, not ad-hoc tool call'
76
76
  }
77
77
  ];
78
+ /**
79
+ * Collapses multi-line strings and extra whitespace into a single normalized shell command string.
80
+ *
81
+ * @param raw - Raw command string.
82
+ * @returns Cleaned single-line command string.
83
+ */
78
84
  export function normalizeCommand(raw) {
79
85
  return raw.replace(/[\r\n\t]+/g, ' ').replace(/\s+/g, ' ').trim();
80
86
  }
@@ -1 +1 @@
1
- {"version":3,"file":"agent-guard-command.js","sourceRoot":"","sources":["../src/agent-guard-command.ts"],"names":[],"mappings":"AAAA;;GAEG;AAaH,MAAM,CAAC,MAAM,iBAAiB,GAA4B;IACxD;QACE,OAAO,EAAE,qGAAqG;QAC9G,MAAM,EAAE,sEAAsE;KAC/E;IACD;QACE,OAAO,EAAE,0DAA0D;QACnE,MAAM,EAAE,uCAAuC;KAChD;IACD;QACE,OAAO,EAAE,iDAAiD;QAC1D,MAAM,EAAE,sCAAsC;KAC/C;IACD;QACE,OAAO,EAAE,8BAA8B;QACvC,MAAM,EAAE,0DAA0D;KACnE;IACD;QACE,OAAO,EAAE,gDAAgD;QACzD,MAAM,EAAE,4CAA4C;KACrD;IACD;QACE,OAAO,EAAE,2CAA2C;QACpD,MAAM,EAAE,sDAAsD;KAC/D;IACD;QACE,OAAO,EAAE,uCAAuC;QAChD,MAAM,EAAE,6BAA6B;KACtC;IACD;QACE,OAAO,EAAE,+CAA+C;QACxD,MAAM,EAAE,2DAA2D;KACpE;IACD;QACE,OAAO,EAAE,6BAA6B;QACtC,MAAM,EAAE,iDAAiD;KAC1D;IACD;QACE,OAAO,EAAE,wBAAwB;QACjC,MAAM,EAAE,qBAAqB;KAC9B;IACD;QACE,OAAO,EAAE,kEAAkE;QAC3E,MAAM,EAAE,qBAAqB;KAC9B;IACD;QACE,OAAO,EAAE,2DAA2D;QACpE,MAAM,EAAE,qBAAqB;KAC9B;IACD;QACE,OAAO,EAAE,iCAAiC;QAC1C,MAAM,EAAE,6BAA6B;KACtC;IACD;QACE,OAAO,EAAE,2CAA2C;QACpD,MAAM,EAAE,oBAAoB;KAC7B;IACD;QACE,OAAO,EAAE,yCAAyC;QAClD,MAAM,EAAE,WAAW;KACpB;IACD;QACE,OAAO,EAAE,6CAA6C;QACtD,MAAM,EAAE,+CAA+C;KACxD;IACD;QACE,OAAO,EAAE,2CAA2C;QACpD,MAAM,EAAE,sCAAsC;KAC/C;IACD;QACE,OAAO,EAAE,gCAAgC;QACzC,MAAM,EAAE,qEAAqE;KAC9E;CACF,CAAC;AAEF,MAAM,UAAU,gBAAgB,CAAC,GAAW;IAC1C,OAAO,GAAG,CAAC,OAAO,CAAC,YAAY,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;AACpE,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,OAAe;IAC5C,MAAM,UAAU,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAC7C,IAAI,CAAC,UAAU;QAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAE3C,KAAK,MAAM,IAAI,IAAI,iBAAiB,EAAE,CAAC;QACrC,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;YAClC,OAAO;gBACL,OAAO,EAAE,IAAI;gBACb,MAAM,EAAE,IAAI,CAAC,MAAM;gBACnB,OAAO,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC;aAClC,CAAC;QACJ,CAAC;IACH,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC;AACjD,CAAC;AAED,eAAe,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,cAAc,EAAE,CAAC"}
1
+ {"version":3,"file":"agent-guard-command.js","sourceRoot":"","sources":["../src/agent-guard-command.ts"],"names":[],"mappings":"AAAA;;GAEG;AAwBH,MAAM,CAAC,MAAM,iBAAiB,GAA4B;IACxD;QACE,OAAO,EAAE,qGAAqG;QAC9G,MAAM,EAAE,sEAAsE;KAC/E;IACD;QACE,OAAO,EAAE,0DAA0D;QACnE,MAAM,EAAE,uCAAuC;KAChD;IACD;QACE,OAAO,EAAE,iDAAiD;QAC1D,MAAM,EAAE,sCAAsC;KAC/C;IACD;QACE,OAAO,EAAE,8BAA8B;QACvC,MAAM,EAAE,0DAA0D;KACnE;IACD;QACE,OAAO,EAAE,gDAAgD;QACzD,MAAM,EAAE,4CAA4C;KACrD;IACD;QACE,OAAO,EAAE,2CAA2C;QACpD,MAAM,EAAE,sDAAsD;KAC/D;IACD;QACE,OAAO,EAAE,uCAAuC;QAChD,MAAM,EAAE,6BAA6B;KACtC;IACD;QACE,OAAO,EAAE,+CAA+C;QACxD,MAAM,EAAE,2DAA2D;KACpE;IACD;QACE,OAAO,EAAE,6BAA6B;QACtC,MAAM,EAAE,iDAAiD;KAC1D;IACD;QACE,OAAO,EAAE,wBAAwB;QACjC,MAAM,EAAE,qBAAqB;KAC9B;IACD;QACE,OAAO,EAAE,kEAAkE;QAC3E,MAAM,EAAE,qBAAqB;KAC9B;IACD;QACE,OAAO,EAAE,2DAA2D;QACpE,MAAM,EAAE,qBAAqB;KAC9B;IACD;QACE,OAAO,EAAE,iCAAiC;QAC1C,MAAM,EAAE,6BAA6B;KACtC;IACD;QACE,OAAO,EAAE,2CAA2C;QACpD,MAAM,EAAE,oBAAoB;KAC7B;IACD;QACE,OAAO,EAAE,yCAAyC;QAClD,MAAM,EAAE,WAAW;KACpB;IACD;QACE,OAAO,EAAE,6CAA6C;QACtD,MAAM,EAAE,+CAA+C;KACxD;IACD;QACE,OAAO,EAAE,2CAA2C;QACpD,MAAM,EAAE,sCAAsC;KAC/C;IACD;QACE,OAAO,EAAE,gCAAgC;QACzC,MAAM,EAAE,qEAAqE;KAC9E;CACF,CAAC;AAEF;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,GAAW;IAC1C,OAAO,GAAG,CAAC,OAAO,CAAC,YAAY,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;AACpE,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,OAAe;IAC5C,MAAM,UAAU,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAC7C,IAAI,CAAC,UAAU;QAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAE3C,KAAK,MAAM,IAAI,IAAI,iBAAiB,EAAE,CAAC;QACrC,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;YAClC,OAAO;gBACL,OAAO,EAAE,IAAI;gBACb,MAAM,EAAE,IAAI,CAAC,MAAM;gBACnB,OAAO,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC;aAClC,CAAC;QACJ,CAAC;IACH,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC;AACjD,CAAC;AAED,eAAe,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,cAAc,EAAE,CAAC"}