azcodr 1.5.1 → 2.0.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.
Files changed (91) hide show
  1. package/.agents/hooks.json.example +42 -42
  2. package/.agents/scripts/safety_guard.sh +143 -34
  3. package/.agents/scripts/verify_completion.sh +90 -27
  4. package/.agents/skills/agentic-architect/SKILL.md +125 -125
  5. package/.agents/skills/agentic-architect/references/agents_md_template.md +62 -62
  6. package/.agents/skills/agentic-architect/references/refinement_workflow.md +32 -32
  7. package/.agents/skills/agentic-architect/references/skill_architecture_inquiry.md +63 -63
  8. package/.agents/skills/agentic-architect/references/skill_template.md +56 -56
  9. package/.agents/skills/agentic-architect/scripts/validate_agentic_configs.sh +402 -401
  10. package/.agents/skills/clean-code-refactor/SKILL.md +91 -91
  11. package/.agents/skills/clean-code-refactor/references/clean_code_smells.md +27 -27
  12. package/.agents/skills/clean-code-refactor/references/design_patterns_ts.md +65 -65
  13. package/.agents/skills/compliance-audit/SKILL.md +120 -120
  14. package/.agents/skills/compliance-audit/references/owasp_top10_controls.md +16 -16
  15. package/.agents/skills/compliance-audit/references/soc2_iso_controls.md +28 -28
  16. package/.agents/skills/lets-build/SKILL.md +173 -173
  17. package/.agents/skills/lets-build/references/architecture_interview_matrix.md +115 -115
  18. package/.agents/skills/lets-build/references/hexagonal_bootstrap_scaffolds.md +160 -160
  19. package/.agents/skills/lets-build/references/project_readme_template.md +79 -79
  20. package/.agents/skills/lets-build/scripts/bootstrap_workspace.sh +419 -255
  21. package/.agents/skills/product-analyst/SKILL.md +154 -154
  22. package/.agents/skills/product-analyst/references/backlog_ordering_techniques.md +107 -107
  23. package/.agents/skills/product-analyst/references/gherkin_patterns.md +46 -46
  24. package/.agents/skills/product-analyst/references/invest_checklist.md +38 -38
  25. package/.agents/skills/product-analyst/references/okr_alignment_guide.md +76 -76
  26. package/.agents/skills/product-analyst/references/smart_tasks.md +59 -59
  27. package/.agents/skills/relentless-questioner/SKILL.md +128 -128
  28. package/.agents/skills/relentless-questioner/references/adaptive_question_trees.md +102 -102
  29. package/.editorconfig +19 -19
  30. package/.github/workflows/ci.yml +167 -56
  31. package/.github/workflows/publish.yml +200 -0
  32. package/.gitignore +40 -25
  33. package/AGENTS.md +103 -102
  34. package/LICENSE +21 -21
  35. package/README.md +168 -154
  36. package/bin/azcodr.js +14 -228
  37. package/docs/knowledge/ubiquitous_language.md +31 -18
  38. package/docs/rules/agentic_configuration.md +259 -259
  39. package/docs/rules/api_architecture.md +179 -179
  40. package/docs/rules/authentication.md +76 -76
  41. package/docs/rules/authorization.md +75 -75
  42. package/docs/rules/caching.md +69 -69
  43. package/docs/rules/clean_code.md +62 -62
  44. package/docs/rules/cloud_native.md +41 -41
  45. package/docs/rules/cqrs.md +203 -203
  46. package/docs/rules/database_design.md +125 -125
  47. package/docs/rules/database_operations.md +69 -69
  48. package/docs/rules/design_patterns.md +98 -98
  49. package/docs/rules/devops_ci_cd.md +76 -76
  50. package/docs/rules/domain_driven_design.md +122 -122
  51. package/docs/rules/error_handling.md +54 -52
  52. package/docs/rules/feature_flags.md +59 -59
  53. package/docs/rules/frontend_architecture.md +157 -157
  54. package/docs/rules/multitenancy_architecture.md +98 -98
  55. package/docs/rules/product_ownership.md +127 -127
  56. package/docs/rules/project_management.md +49 -49
  57. package/docs/rules/relentless_questioning.md +52 -52
  58. package/docs/rules/requirements_engineering.md +98 -98
  59. package/docs/rules/security_compliance.md +53 -53
  60. package/docs/rules/server_driven_ui.md +88 -88
  61. package/docs/rules/test_driven_development.md +185 -185
  62. package/docs/rules/transactional_email.md +27 -27
  63. package/docs/rules/type_safety.md +65 -65
  64. package/docs/rules/ui_ux_architecture.md +150 -150
  65. package/docs/rules/workflow_state_machines.md +117 -117
  66. package/lib/cli-parse.js +51 -0
  67. package/lib/cli-target.js +109 -0
  68. package/lib/cli.js +180 -0
  69. package/lib/errors.js +28 -0
  70. package/lib/git.js +29 -0
  71. package/lib/guards.js +96 -0
  72. package/lib/index.d.ts +199 -134
  73. package/lib/index.js +5 -5
  74. package/lib/links.js +123 -0
  75. package/lib/permissions.js +44 -0
  76. package/lib/repo.js +90 -0
  77. package/lib/scaffold.js +238 -399
  78. package/memory.md +119 -36
  79. package/package.json +65 -62
  80. package/scripts/test_coverage.js +66 -38
  81. package/scripts/validate/adr.js +151 -0
  82. package/scripts/validate/io.js +84 -0
  83. package/scripts/validate/links.js +167 -0
  84. package/scripts/validate/parity.js +124 -0
  85. package/scripts/validate/root.js +184 -0
  86. package/scripts/validate/rules.js +44 -0
  87. package/scripts/validate/skills.js +96 -0
  88. package/scripts/validate/text.js +29 -0
  89. package/scripts/validate-cli.js +13 -0
  90. package/scripts/validate.js +112 -218
  91. package/.github/copilot-instructions.md +0 -1
package/lib/index.d.ts CHANGED
@@ -1,134 +1,199 @@
1
- /**
2
- * Enterprise Multi-Tenant Architecture & Agentic Engineering Starter Template
3
- * Programmatic API Definitions
4
- */
5
-
6
- export interface ScaffoldOptions {
7
- /** Target directory path where template should be scaffolded (default: process.cwd()) */
8
- targetDir?: string;
9
- /** Force overwrite if target directory is non-empty (default: false) */
10
- force?: boolean;
11
- /** Skip git repository initialization (default: false) */
12
- noGit?: boolean;
13
- /** Custom template root directory (default: azcodr root) */
14
- templateDir?: string;
15
- /** Simulate scaffolding without writing files or running git (default: false) */
16
- dryRun?: boolean;
17
- /** Suppress console output messages (default: false) */
18
- silent?: boolean;
19
- }
20
-
21
- export interface ScaffoldResult {
22
- /** Whether the scaffolding succeeded */
23
- success: boolean;
24
- /** Absolute resolved path of the target directory */
25
- targetDir: string;
26
- /** Whether git init was successfully executed */
27
- gitInitialized: boolean;
28
- /** Whether execution ran in dry-run simulation mode */
29
- dryRun: boolean;
30
- /** List of files, directories, and symlinks created or simulated */
31
- actions: string[];
32
- }
33
-
34
- export interface ValidateTargetOptions {
35
- /** Custom template root directory */
36
- templateDir?: string;
37
- /** Force allow non-empty directory */
38
- force?: boolean;
39
- }
40
-
41
- export interface InitGitOptions {
42
- /** If true, skips git initialization */
43
- noGit?: boolean;
44
- /** If true, simulates git initialization without executing */
45
- dryRun?: boolean;
46
- }
47
-
48
- export interface CopyTemplateOptions {
49
- /** If true, simulates copy without writing to disk */
50
- dryRun?: boolean;
51
- }
52
-
53
- /**
54
- * High-level orchestration function to scaffold the azcodr workspace into targetDir.
55
- */
56
- export function scaffold(options?: ScaffoldOptions): ScaffoldResult;
57
-
58
- /**
59
- * Validates the target directory to ensure it is suitable for scaffolding.
60
- */
61
- export function validateTarget(targetDir: string, options?: ValidateTargetOptions): void;
62
-
63
- /**
64
- * Copies template items into target directory and sets up symlinks and permissions.
65
- */
66
- export function copyTemplate(
67
- targetDir: string,
68
- templateDir?: string,
69
- options?: CopyTemplateOptions
70
- ): string[];
71
-
72
- /**
73
- * Safely creates or updates a symbolic link, falling back to a file copy if symlinks are unsupported.
74
- */
75
- export function ensureSymlink(
76
- targetDir: string,
77
- linkName: string,
78
- targetFileName: string,
79
- dryRun?: boolean
80
- ): boolean;
81
-
82
- /**
83
- * Creates a symlink, falling back to a text pointer (not a full copy).
84
- */
85
- export function ensureSymlinkOrPointer(
86
- targetDir: string,
87
- linkName: string,
88
- targetFileName: string,
89
- dryRun?: boolean
90
- ): boolean;
91
-
92
- /**
93
- * Detects whether linkName and targetFileName refer to the same entry on a case-insensitive filesystem.
94
- */
95
- export function isSameCaseInsensitiveFile(
96
- targetDir: string,
97
- linkName: string,
98
- targetFileName: string
99
- ): boolean;
100
-
101
- /**
102
- * Ensures all bash scripts in skill directories have executable permissions (0o755).
103
- */
104
- export function makeScriptsExecutable(targetDir: string, dryRun?: boolean): string[];
105
-
106
- /**
107
- * Initializes a git repository in the target directory if not already inside one.
108
- */
109
- export function initGit(targetDir: string, options?: InitGitOptions): boolean;
110
-
111
- /**
112
- * Returns the root path to the azcodr template files.
113
- */
114
- export function getTemplateDir(): string;
115
-
116
- /**
117
- * Array of essential template files and directories copied during scaffolding.
118
- */
119
- export const TEMPLATE_ITEMS: readonly string[];
120
-
121
- declare const defaultExport: {
122
- scaffold: typeof scaffold;
123
- validateTarget: typeof validateTarget;
124
- copyTemplate: typeof copyTemplate;
125
- ensureSymlink: typeof ensureSymlink;
126
- ensureSymlinkOrPointer: typeof ensureSymlinkOrPointer;
127
- isSameCaseInsensitiveFile: typeof isSameCaseInsensitiveFile;
128
- makeScriptsExecutable: typeof makeScriptsExecutable;
129
- initGit: typeof initGit;
130
- getTemplateDir: typeof getTemplateDir;
131
- TEMPLATE_ITEMS: typeof TEMPLATE_ITEMS;
132
- };
133
-
134
- export default defaultExport;
1
+ /**
2
+ * Enterprise Multi-Tenant Architecture & Agentic Engineering Starter Template
3
+ * Programmatic API Definitions
4
+ */
5
+
6
+ export interface ScaffoldOptions {
7
+ /** Target directory path where template should be scaffolded (default: process.cwd()) */
8
+ targetDir?: string;
9
+ /** Force overwrite if target directory is non-empty (default: false) */
10
+ force?: boolean;
11
+ /** Skip git repository initialization (default: false) */
12
+ noGit?: boolean;
13
+ /** Custom template root directory (default: azcodr root) */
14
+ templateDir?: string;
15
+ /** Simulate scaffolding without writing files or running git (default: false) */
16
+ dryRun?: boolean;
17
+ /** Suppress console output messages (default: false) */
18
+ silent?: boolean;
19
+ }
20
+
21
+ export interface ScaffoldResult {
22
+ /** Whether the scaffolding succeeded */
23
+ success: boolean;
24
+ /** Absolute resolved path of the target directory */
25
+ targetDir: string;
26
+ /** Whether git init was successfully executed */
27
+ gitInitialized: boolean;
28
+ /** Whether execution ran in dry-run simulation mode */
29
+ dryRun: boolean;
30
+ /** List of files, directories, and symlinks created or simulated */
31
+ actions: string[];
32
+ }
33
+
34
+ export interface ValidateTargetOptions {
35
+ /** Custom template root directory */
36
+ templateDir?: string;
37
+ /** Force allow non-empty directory */
38
+ force?: boolean;
39
+ /**
40
+ * Bypass the protected-target guard (filesystem root, home, home's parent,
41
+ * template ancestors). The CLI never passes this. Programmatic use only.
42
+ */
43
+ allowProtected?: boolean;
44
+ }
45
+
46
+ export interface InitGitOptions {
47
+ /** If true, skips git initialization */
48
+ noGit?: boolean;
49
+ /** If true, simulates git initialization without executing */
50
+ dryRun?: boolean;
51
+ }
52
+
53
+ export interface CopyTemplateOptions {
54
+ /** If true, simulates copy without writing to disk */
55
+ dryRun?: boolean;
56
+ }
57
+
58
+ /**
59
+ * Machine-readable failure codes thrown by this library.
60
+ *
61
+ * Branch on `error.code`, never on `error.message`: message wording may change
62
+ * between releases, codes are part of the public contract.
63
+ */
64
+ export type ScaffoldErrorCode =
65
+ | 'E_TARGET_IS_TEMPLATE'
66
+ | 'E_TARGET_NOT_EMPTY'
67
+ | 'E_TARGET_IS_PROTECTED'
68
+ | 'E_GIT_ARGS_INVALID'
69
+ | 'E_GIT_BLOCKED'
70
+ | 'E_PATH_ESCAPE';
71
+
72
+ /** Error type thrown by every azcodr API. Always carries a `code`. */
73
+ export interface ScaffoldErrorShape extends Error {
74
+ name: 'ScaffoldError';
75
+ code: ScaffoldErrorCode;
76
+ }
77
+
78
+ export declare class ScaffoldError extends Error implements ScaffoldErrorShape {
79
+ constructor(code: ScaffoldErrorCode, message: string);
80
+ name: 'ScaffoldError';
81
+ code: ScaffoldErrorCode;
82
+ }
83
+
84
+ /** Human-readable prefix for each code, as thrown. */
85
+ export declare const ERROR_CODES: Readonly<Record<ScaffoldErrorCode, string>>;
86
+
87
+ /**
88
+ * High-level orchestration function to scaffold the azcodr workspace into targetDir.
89
+ */
90
+ export function scaffold(options?: ScaffoldOptions): ScaffoldResult;
91
+
92
+ /**
93
+ * Validates the target directory to ensure it is suitable for scaffolding.
94
+ */
95
+ export function validateTarget(targetDir: string, options?: ValidateTargetOptions): void;
96
+
97
+ /**
98
+ * Copies template items into target directory and sets up symlinks and permissions.
99
+ */
100
+ export function copyTemplate(
101
+ targetDir: string,
102
+ templateDir?: string,
103
+ options?: CopyTemplateOptions
104
+ ): string[];
105
+
106
+ /**
107
+ * Safely creates or updates a symbolic link, falling back to a file copy if symlinks are unsupported.
108
+ */
109
+ export function ensureSymlink(options: {
110
+ targetDir: string;
111
+ linkName: string;
112
+ targetFileName: string;
113
+ dryRun?: boolean;
114
+ }): boolean;
115
+
116
+ /**
117
+ * Creates a symlink, falling back to a text pointer (not a full copy).
118
+ */
119
+ export function ensureSymlinkOrPointer(options: {
120
+ targetDir: string;
121
+ linkName: string;
122
+ targetFileName: string;
123
+ dryRun?: boolean;
124
+ }): boolean;
125
+
126
+ /**
127
+ * Detects whether linkName and targetFileName refer to the same entry on a case-insensitive filesystem.
128
+ */
129
+ export function isSameCaseInsensitiveFile(
130
+ targetDir: string,
131
+ linkName: string,
132
+ targetFileName: string
133
+ ): boolean;
134
+
135
+ /**
136
+ * Ensures all bash scripts in skill directories have executable permissions (0o755).
137
+ */
138
+ export function makeScriptsExecutable(targetDir: string, dryRun?: boolean): string[];
139
+
140
+ /**
141
+ * Initializes a git repository in the target directory if not already inside one.
142
+ */
143
+ export function initGit(targetDir: string, options?: InitGitOptions): boolean;
144
+
145
+ /**
146
+ * Detects whether targetDir is already inside an existing Git worktree.
147
+ * Shell access is limited to `git rev-parse` via a shell-free allowlisted spawn.
148
+ */
149
+ export function isInsideGitWorkTree(targetDir: string): boolean;
150
+
151
+ /**
152
+ * Spawns `git` with argv (never a shell string) through an explicit
153
+ * subcommand allowlist. @throws ScaffoldError with E_GIT_ARGS_INVALID or E_GIT_BLOCKED.
154
+ */
155
+ export function runGit(args: string[], options?: Record<string, unknown>): string;
156
+
157
+ /**
158
+ * Scope guard: throws if `candidate` resolves outside `root`.
159
+ * @throws ScaffoldError with E_PATH_ESCAPE.
160
+ */
161
+ export function assertInside(root: string, candidate: string, message?: string): void;
162
+
163
+ /**
164
+ * Returns true when `targetDir` is a protected system location the scaffolder
165
+ * must never write into (filesystem root, home, home's parent, template
166
+ * ancestors, symlink aliases). @throws never; pure predicate.
167
+ */
168
+ export function isProtectedTarget(targetDir: string, options?: ValidateTargetOptions): boolean;
169
+
170
+ /**
171
+ * Returns the root path to the azcodr template files.
172
+ */
173
+ export function getTemplateDir(): string;
174
+
175
+ /**
176
+ * Array of essential template files and directories copied during scaffolding.
177
+ */
178
+ export const TEMPLATE_ITEMS: readonly string[];
179
+
180
+ declare const defaultExport: {
181
+ scaffold: typeof scaffold;
182
+ validateTarget: typeof validateTarget;
183
+ copyTemplate: typeof copyTemplate;
184
+ ensureSymlink: typeof ensureSymlink;
185
+ ensureSymlinkOrPointer: typeof ensureSymlinkOrPointer;
186
+ isSameCaseInsensitiveFile: typeof isSameCaseInsensitiveFile;
187
+ makeScriptsExecutable: typeof makeScriptsExecutable;
188
+ initGit: typeof initGit;
189
+ isInsideGitWorkTree: typeof isInsideGitWorkTree;
190
+ runGit: typeof runGit;
191
+ assertInside: typeof assertInside;
192
+ isProtectedTarget: typeof isProtectedTarget;
193
+ getTemplateDir: typeof getTemplateDir;
194
+ TEMPLATE_ITEMS: typeof TEMPLATE_ITEMS;
195
+ ScaffoldError: typeof ScaffoldError;
196
+ ERROR_CODES: typeof ERROR_CODES;
197
+ };
198
+
199
+ export default defaultExport;
package/lib/index.js CHANGED
@@ -1,5 +1,5 @@
1
- 'use strict';
2
-
3
- const scaffold = require('./scaffold.js');
4
-
5
- module.exports = scaffold;
1
+ 'use strict';
2
+
3
+ const scaffold = require('./scaffold.js');
4
+
5
+ module.exports = scaffold;
package/lib/links.js ADDED
@@ -0,0 +1,123 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+
6
+ /**
7
+ * Detects whether linkName and targetFileName refer to the same entry on a case-insensitive filesystem.
8
+ * Uses exact directory listing (not existsSync, which lies on case-insensitive systems).
9
+ */
10
+ function namesDifferOnlyByCase(linkName, targetFileName) {
11
+ if (linkName.toLowerCase() !== targetFileName.toLowerCase()) return false;
12
+ return linkName !== targetFileName;
13
+ }
14
+
15
+ function readDirEntries(targetDir) {
16
+ try {
17
+ return fs.readdirSync(targetDir);
18
+ } catch {
19
+ return null;
20
+ }
21
+ }
22
+
23
+ function bothEntriesExist({ targetDir, linkName, targetFileName, entries }) {
24
+ if (!entries.includes(targetFileName) || !entries.includes(linkName)) return false;
25
+ try {
26
+ fs.lstatSync(path.join(targetDir, targetFileName));
27
+ fs.lstatSync(path.join(targetDir, linkName));
28
+ return true;
29
+ } catch {
30
+ return false;
31
+ }
32
+ }
33
+
34
+ function accessiblePair(targetPath, linkPath) {
35
+ // Case-insensitive collision: the listing holds one spelling while both
36
+ // paths resolve. Verify the entries are accessible (throws on disk error).
37
+ if (!fs.existsSync(targetPath) || !fs.existsSync(linkPath)) return false;
38
+ try {
39
+ fs.lstatSync(targetPath);
40
+ fs.lstatSync(linkPath);
41
+ } catch {
42
+ return false;
43
+ }
44
+ return true;
45
+ }
46
+
47
+ function isSameCaseInsensitiveFile(targetDir, linkName, targetFileName) {
48
+ if (linkName === targetFileName) return true;
49
+ if (!namesDifferOnlyByCase(linkName, targetFileName)) return false;
50
+ const entries = readDirEntries(targetDir);
51
+ if (entries === null) return false;
52
+ if (bothEntriesExist({ targetDir, linkName, targetFileName, entries })) {
53
+ return !fs.lstatSync(path.join(targetDir, linkName)).isSymbolicLink();
54
+ }
55
+ const targetPath = path.join(targetDir, targetFileName);
56
+ const linkPath = path.join(targetDir, linkName);
57
+ return accessiblePair(targetPath, linkPath);
58
+ }
59
+
60
+ function removeIfExists(linkPath) {
61
+ try {
62
+ fs.lstatSync(linkPath);
63
+ fs.rmSync(linkPath, { force: true });
64
+ } catch {
65
+ // Path does not exist, proceed
66
+ }
67
+ }
68
+
69
+ function copyFallback(targetDir, linkName, targetFileName) {
70
+ // Fallback if environment (e.g., certain Windows configs) prevents symlink creation
71
+ const sourceFile = path.resolve(targetDir, targetFileName);
72
+ if (fs.existsSync(sourceFile)) {
73
+ fs.copyFileSync(sourceFile, path.join(targetDir, linkName));
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Safely creates or updates a symbolic link, falling back to a file copy if symlinks are unsupported.
79
+ */
80
+ function ensureSymlink({ targetDir, linkName, targetFileName, dryRun = false }) {
81
+ if (dryRun) return true;
82
+ if (isSameCaseInsensitiveFile(targetDir, linkName, targetFileName)) return true;
83
+ const linkPath = path.join(targetDir, linkName);
84
+ removeIfExists(linkPath);
85
+ try {
86
+ fs.symlinkSync(targetFileName, linkPath, 'file');
87
+ return true;
88
+ } catch {
89
+ copyFallback(targetDir, linkName, targetFileName);
90
+ return true;
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Creates a symlink, falling back to a text pointer (not a full copy).
96
+ * Used for .github/copilot-instructions.md where a full AGENTS.md copy would
97
+ * break relative markdown links (they resolve from .github/, not root).
98
+ */
99
+ function ensureSymlinkOrPointer({ targetDir, linkName, targetFileName, dryRun = false }) {
100
+ if (dryRun) return true;
101
+ const linkPath = path.join(targetDir, linkName);
102
+ try {
103
+ try {
104
+ fs.lstatSync(linkPath);
105
+ const existing = fs.existsSync(linkPath) ? fs.readFileSync(linkPath, 'utf-8') : '';
106
+ if (existing.trim() === targetFileName) return true;
107
+ fs.rmSync(linkPath, { force: true });
108
+ } catch {
109
+ // Path does not exist, proceed
110
+ }
111
+ fs.symlinkSync(targetFileName, linkPath, 'file');
112
+ return true;
113
+ } catch {
114
+ fs.writeFileSync(linkPath, `${targetFileName}\n`, 'utf-8');
115
+ return true;
116
+ }
117
+ }
118
+
119
+ module.exports = {
120
+ isSameCaseInsensitiveFile,
121
+ ensureSymlink,
122
+ ensureSymlinkOrPointer
123
+ };
@@ -0,0 +1,44 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+
6
+ function chmodShFile(filePath, dryRun) {
7
+ if (dryRun) return;
8
+ try {
9
+ fs.chmodSync(filePath, 0o755);
10
+ } catch {
11
+ // Non-critical if filesystem does not support POSIX permissions
12
+ }
13
+ }
14
+
15
+ function chmodShFilesIn(scriptsDir, dryRun) {
16
+ const modified = [];
17
+ if (!fs.existsSync(scriptsDir) || !fs.statSync(scriptsDir).isDirectory()) return modified;
18
+ const files = fs.readdirSync(scriptsDir);
19
+ for (const file of files) {
20
+ if (!file.endsWith('.sh')) continue;
21
+ const filePath = path.join(scriptsDir, file);
22
+ modified.push(filePath);
23
+ chmodShFile(filePath, dryRun);
24
+ }
25
+ return modified;
26
+ }
27
+
28
+ /**
29
+ * Ensures all bash scripts in agent and skill directories have executable permissions (0o755).
30
+ */
31
+ function makeScriptsExecutable(targetDir, dryRun = false) {
32
+ const agentScriptsDir = path.join(targetDir, '.agents', 'scripts');
33
+ const modified = chmodShFilesIn(agentScriptsDir, dryRun);
34
+ const skillsDir = path.join(targetDir, '.agents', 'skills');
35
+ if (!fs.existsSync(skillsDir)) return modified;
36
+ const skills = fs.readdirSync(skillsDir);
37
+ for (const skill of skills) {
38
+ const scriptsDir = path.join(skillsDir, skill, 'scripts');
39
+ modified.push(...chmodShFilesIn(scriptsDir, dryRun));
40
+ }
41
+ return modified;
42
+ }
43
+
44
+ module.exports = { makeScriptsExecutable };
package/lib/repo.js ADDED
@@ -0,0 +1,90 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+ const { runGit } = require('./git.js');
6
+
7
+ const INITIAL_COMMIT_MESSAGE = 'chore: initial scaffold from azcodr template';
8
+
9
+ /**
10
+ * Detects whether targetDir is already inside an existing Git worktree.
11
+ * Socket note: shell access is limited to `git rev-parse` via runGit (no shell).
12
+ */
13
+ function isInsideGitWorkTree(targetDir) {
14
+ try {
15
+ const checkDir = fs.existsSync(targetDir) ? targetDir : path.dirname(targetDir);
16
+ const out = runGit(['rev-parse', '--is-inside-work-tree'], {
17
+ cwd: checkDir,
18
+ stdio: ['ignore', 'pipe', 'ignore']
19
+ });
20
+ return String(out).trim() === 'true';
21
+ } catch {
22
+ return false;
23
+ }
24
+ }
25
+
26
+ function renameBranchMain(targetDir) {
27
+ try {
28
+ runGit(['branch', '-m', 'main'], { cwd: targetDir, stdio: 'ignore' });
29
+ } catch {
30
+ // Non-critical if branch rename fails
31
+ }
32
+ }
33
+
34
+ function initRepository(targetDir) {
35
+ try {
36
+ runGit(['init', '-b', 'main', '-q'], { cwd: targetDir, stdio: 'ignore' });
37
+ } catch {
38
+ runGit(['init', '-q'], { cwd: targetDir, stdio: 'ignore' });
39
+ renameBranchMain(targetDir);
40
+ }
41
+ }
42
+
43
+ function commitWithFallbackIdentity(targetDir) {
44
+ const options = { cwd: targetDir, stdio: 'ignore' };
45
+ try {
46
+ runGit(['commit', '-q', '-m', INITIAL_COMMIT_MESSAGE], options);
47
+ } catch {
48
+ runGit(['commit', '-q', '-m', INITIAL_COMMIT_MESSAGE], {
49
+ ...options,
50
+ env: {
51
+ ...process.env,
52
+ GIT_AUTHOR_NAME: 'Subodh Khanal',
53
+ GIT_AUTHOR_EMAIL: 'prosubodh@gmail.com',
54
+ GIT_COMMITTER_NAME: 'Subodh Khanal',
55
+ GIT_COMMITTER_EMAIL: 'prosubodh@gmail.com'
56
+ }
57
+ });
58
+ }
59
+ }
60
+
61
+ function initialCommit(targetDir) {
62
+ try {
63
+ runGit(['add', '-A'], { cwd: targetDir, stdio: 'ignore' });
64
+ commitWithFallbackIdentity(targetDir);
65
+ } catch {
66
+ // Non-critical if initial commit fails
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Initializes a git repository in the target directory if not already inside one.
72
+ * Socket note: only allowlisted `git init/branch/add/commit` via execFileSync, cwd scoped.
73
+ */
74
+ function initGit(targetDir, options = {}) {
75
+ const { noGit = false, dryRun = false } = options;
76
+ if (noGit) return false;
77
+ const gitDir = path.join(targetDir, '.git');
78
+ if (fs.existsSync(gitDir)) return false;
79
+ if (isInsideGitWorkTree(targetDir)) return false;
80
+ if (dryRun) return true;
81
+ try {
82
+ initRepository(targetDir);
83
+ initialCommit(targetDir);
84
+ return true;
85
+ } catch {
86
+ return false;
87
+ }
88
+ }
89
+
90
+ module.exports = { isInsideGitWorkTree, initGit };