azcodr 1.5.2 → 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 (93) hide show
  1. package/.agents/hooks.json +42 -42
  2. package/.agents/hooks.json.example +42 -42
  3. package/.agents/mcp_config.json.example +29 -29
  4. package/.agents/scripts/safety_guard.sh +143 -34
  5. package/.agents/scripts/verify_completion.sh +90 -27
  6. package/.agents/skills/agentic-architect/SKILL.md +125 -125
  7. package/.agents/skills/agentic-architect/references/agents_md_template.md +62 -62
  8. package/.agents/skills/agentic-architect/references/refinement_workflow.md +32 -32
  9. package/.agents/skills/agentic-architect/references/skill_architecture_inquiry.md +63 -63
  10. package/.agents/skills/agentic-architect/references/skill_template.md +56 -56
  11. package/.agents/skills/agentic-architect/scripts/validate_agentic_configs.sh +402 -402
  12. package/.agents/skills/clean-code-refactor/SKILL.md +91 -91
  13. package/.agents/skills/clean-code-refactor/references/clean_code_smells.md +27 -27
  14. package/.agents/skills/clean-code-refactor/references/design_patterns_ts.md +65 -65
  15. package/.agents/skills/compliance-audit/SKILL.md +120 -120
  16. package/.agents/skills/compliance-audit/references/owasp_top10_controls.md +16 -16
  17. package/.agents/skills/compliance-audit/references/soc2_iso_controls.md +28 -28
  18. package/.agents/skills/lets-build/SKILL.md +173 -173
  19. package/.agents/skills/lets-build/references/architecture_interview_matrix.md +115 -115
  20. package/.agents/skills/lets-build/references/hexagonal_bootstrap_scaffolds.md +160 -160
  21. package/.agents/skills/lets-build/references/project_readme_template.md +79 -79
  22. package/.agents/skills/lets-build/scripts/bootstrap_workspace.sh +419 -255
  23. package/.agents/skills/product-analyst/SKILL.md +154 -154
  24. package/.agents/skills/product-analyst/references/backlog_ordering_techniques.md +107 -107
  25. package/.agents/skills/product-analyst/references/gherkin_patterns.md +46 -46
  26. package/.agents/skills/product-analyst/references/invest_checklist.md +38 -38
  27. package/.agents/skills/product-analyst/references/okr_alignment_guide.md +76 -76
  28. package/.agents/skills/product-analyst/references/smart_tasks.md +59 -59
  29. package/.agents/skills/relentless-questioner/SKILL.md +128 -128
  30. package/.agents/skills/relentless-questioner/references/adaptive_question_trees.md +102 -102
  31. package/.editorconfig +19 -19
  32. package/.github/workflows/ci.yml +167 -78
  33. package/.github/workflows/publish.yml +200 -0
  34. package/.gitignore +40 -25
  35. package/AGENTS.md +103 -102
  36. package/LICENSE +21 -21
  37. package/README.md +168 -165
  38. package/bin/azcodr.js +14 -228
  39. package/docs/knowledge/ubiquitous_language.md +31 -18
  40. package/docs/rules/agentic_configuration.md +259 -259
  41. package/docs/rules/api_architecture.md +179 -179
  42. package/docs/rules/authentication.md +76 -76
  43. package/docs/rules/authorization.md +75 -75
  44. package/docs/rules/caching.md +69 -69
  45. package/docs/rules/clean_code.md +62 -62
  46. package/docs/rules/cloud_native.md +41 -41
  47. package/docs/rules/cqrs.md +203 -203
  48. package/docs/rules/database_design.md +125 -125
  49. package/docs/rules/database_operations.md +69 -69
  50. package/docs/rules/design_patterns.md +98 -98
  51. package/docs/rules/devops_ci_cd.md +76 -76
  52. package/docs/rules/domain_driven_design.md +122 -122
  53. package/docs/rules/error_handling.md +54 -52
  54. package/docs/rules/feature_flags.md +59 -59
  55. package/docs/rules/frontend_architecture.md +157 -157
  56. package/docs/rules/multitenancy_architecture.md +98 -98
  57. package/docs/rules/product_ownership.md +127 -127
  58. package/docs/rules/project_management.md +49 -49
  59. package/docs/rules/relentless_questioning.md +52 -52
  60. package/docs/rules/requirements_engineering.md +98 -98
  61. package/docs/rules/security_compliance.md +53 -53
  62. package/docs/rules/server_driven_ui.md +88 -88
  63. package/docs/rules/test_driven_development.md +185 -185
  64. package/docs/rules/transactional_email.md +27 -27
  65. package/docs/rules/type_safety.md +65 -65
  66. package/docs/rules/ui_ux_architecture.md +150 -150
  67. package/docs/rules/workflow_state_machines.md +117 -117
  68. package/lib/cli-parse.js +51 -0
  69. package/lib/cli-target.js +109 -0
  70. package/lib/cli.js +180 -0
  71. package/lib/errors.js +28 -0
  72. package/lib/git.js +29 -0
  73. package/lib/guards.js +96 -0
  74. package/lib/index.d.ts +199 -134
  75. package/lib/index.js +5 -5
  76. package/lib/links.js +123 -0
  77. package/lib/permissions.js +44 -0
  78. package/lib/repo.js +90 -0
  79. package/lib/scaffold.js +238 -448
  80. package/memory.md +119 -36
  81. package/package.json +65 -62
  82. package/scripts/test_coverage.js +66 -38
  83. package/scripts/validate/adr.js +151 -0
  84. package/scripts/validate/io.js +84 -0
  85. package/scripts/validate/links.js +167 -0
  86. package/scripts/validate/parity.js +124 -0
  87. package/scripts/validate/root.js +184 -0
  88. package/scripts/validate/rules.js +44 -0
  89. package/scripts/validate/skills.js +96 -0
  90. package/scripts/validate/text.js +29 -0
  91. package/scripts/validate-cli.js +13 -0
  92. package/scripts/validate.js +140 -258
  93. package/.github/copilot-instructions.md +0 -1
@@ -0,0 +1,109 @@
1
+ 'use strict';
2
+
3
+ const path = require('node:path');
4
+ const readline = require('node:readline');
5
+ const fs = require('node:fs');
6
+ const { isProtectedTarget } = require('./guards.js');
7
+
8
+ function askQuestion(query, { input = process.stdin, output = process.stdout } = {}) {
9
+ const rl = readline.createInterface({ input, output });
10
+
11
+ return new Promise((resolve) => {
12
+ let resolved = false;
13
+ rl.question(query, (answer) => {
14
+ if (!resolved) {
15
+ resolved = true;
16
+ rl.close();
17
+ resolve(answer.trim());
18
+ }
19
+ });
20
+ rl.on('close', () => {
21
+ if (!resolved) {
22
+ resolved = true;
23
+ resolve('');
24
+ }
25
+ });
26
+ });
27
+ }
28
+
29
+ async function promptForTargetDir(io) {
30
+ const { stdin, stdout } = io;
31
+ if (!stdin.isTTY) return '.';
32
+ const answer = await askQuestion('? Where would you like to initialize your project? (./) ', {
33
+ input: stdin,
34
+ output: stdout
35
+ });
36
+ return answer || '.';
37
+ }
38
+
39
+ function rejectBadTarget(resolvedTarget, templateDir) {
40
+ if (resolvedTarget === templateDir) {
41
+ return `Cannot scaffold into the template directory itself: ${resolvedTarget}`;
42
+ }
43
+ // Fail fast on protected locations BEFORE the non-empty prompt. The prompt
44
+ // asks "Continue? (y/N)" for ordinary project directories; answering "y"
45
+ // for ~ or / must not be possible. The library enforces this again in
46
+ // validateTarget, so this is defence in depth, not the sole check.
47
+ if (isProtectedTarget(resolvedTarget, { templateDir })) {
48
+ return `Refusing to scaffold into protected directory: ${resolvedTarget}. Choose a project subdirectory instead.`;
49
+ }
50
+ return null;
51
+ }
52
+
53
+ async function resolveTargetDir(targetDir, io) {
54
+ const { cwd, templateDir } = io;
55
+ const chosen = targetDir || await promptForTargetDir(io);
56
+ const resolvedTarget = path.resolve(cwd, chosen);
57
+ const rejection = rejectBadTarget(resolvedTarget, templateDir);
58
+ if (rejection !== null) return { resolvedTarget, chosen, error: rejection };
59
+ return { resolvedTarget, chosen, error: null };
60
+ }
61
+
62
+ function targetStatus(resolvedTarget) {
63
+ if (!fs.existsSync(resolvedTarget)) return { status: 'missing', count: 0 };
64
+ if (!fs.statSync(resolvedTarget).isDirectory()) return { status: 'not-a-directory', count: 0 };
65
+ const count = fs.readdirSync(resolvedTarget).length;
66
+ return { status: count === 0 ? 'empty' : 'non-empty', count };
67
+ }
68
+
69
+ async function confirmOverwrite(targetDir, count, io) {
70
+ const { stdin, stdout, out } = io;
71
+ if (!stdin.isTTY) return { confirmed: false, aborted: false };
72
+ const confirm = await askQuestion(
73
+ `⚠️ Target directory '${targetDir}' is not empty (${count} items). Continue? (y/N) `,
74
+ { input: stdin, output: stdout }
75
+ );
76
+ if (confirm.toLowerCase() !== 'y' && confirm.toLowerCase() !== 'yes') {
77
+ out('Scaffolding aborted.');
78
+ return { confirmed: false, aborted: true };
79
+ }
80
+ return { confirmed: true, aborted: false };
81
+ }
82
+
83
+ /**
84
+ * Ensures the target may be written. Returns { force } on success or
85
+ * { exitCode, message? } when the CLI must stop before scaffolding.
86
+ */
87
+ async function ensureWritableTarget({ chosen, resolvedTarget, force, io }) {
88
+ const { err } = io;
89
+ const { status, count } = targetStatus(resolvedTarget);
90
+ if (status === 'missing' || status === 'empty' || force) return { force };
91
+ if (status === 'not-a-directory') {
92
+ return { exitCode: 1, message: `Target '${resolvedTarget}' already exists and is not a directory.` };
93
+ }
94
+ const { confirmed, aborted } = await confirmOverwrite(chosen, count, io);
95
+ if (aborted) return { exitCode: 0, message: null };
96
+ if (confirmed) return { force: true };
97
+ err(`Target directory '${resolvedTarget}' is not empty. Use --force to proceed.`);
98
+ return { exitCode: 1, message: null };
99
+ }
100
+
101
+ module.exports = {
102
+ askQuestion,
103
+ promptForTargetDir,
104
+ rejectBadTarget,
105
+ resolveTargetDir,
106
+ targetStatus,
107
+ confirmOverwrite,
108
+ ensureWritableTarget
109
+ };
package/lib/cli.js ADDED
@@ -0,0 +1,180 @@
1
+ 'use strict';
2
+
3
+ const { scaffold, getTemplateDir } = require('./scaffold.js');
4
+ const { parseArgs } = require('./cli-parse.js');
5
+ const { askQuestion, resolveTargetDir, ensureWritableTarget } = require('./cli-target.js');
6
+ const pkg = require('../package.json');
7
+
8
+ function printHelp(out = console.log) {
9
+ out(`
10
+ azcodr v${pkg.version}
11
+ Enterprise Multi-Tenant Architecture & Agentic Engineering Starter Template
12
+
13
+ Usage:
14
+ npx azcodr [directory] [options]
15
+
16
+ Commands:
17
+ [directory] Scaffold azcodr template into directory (default: current directory)
18
+
19
+ Options:
20
+ -d, --dry-run Simulate scaffolding without modifying filesystem
21
+ -s, --silent Suppress console output messages
22
+ -f, --force Overwrite existing files in target directory without confirmation
23
+ --no-git Do not initialize a git repository
24
+ -v, --version Display version number
25
+ -h, --help Display this help message
26
+
27
+ Examples:
28
+ npx azcodr my-project
29
+ npx azcodr . --dry-run
30
+ npx azcodr . --force
31
+ `);
32
+ }
33
+
34
+ function printVersion(out = console.log) {
35
+ out(pkg.version);
36
+ }
37
+
38
+ function normalizeIo(io = {}) {
39
+ const {
40
+ out = console.log,
41
+ err = console.error,
42
+ exit = process.exit,
43
+ stdin = process.stdin,
44
+ stdout = process.stdout,
45
+ cwd = process.cwd(),
46
+ templateDir = getTemplateDir(),
47
+ scaffold: scaffoldFn = scaffold
48
+ } = io;
49
+ return { out, err, exit, stdin, stdout, cwd, templateDir, scaffoldFn };
50
+ }
51
+
52
+ function outBanner(fullIo) {
53
+ fullIo.out('\n🚀 azcodr - Enterprise Multi-Tenant Architecture & Agentic Engineering\n');
54
+ }
55
+
56
+ function handleTerminal(parsed, io) {
57
+ const { out, err, exit } = io;
58
+ if (parsed.terminal === 'help') {
59
+ printHelp(out);
60
+ return exit(0);
61
+ }
62
+ if (parsed.terminal === 'version') {
63
+ printVersion(out);
64
+ return exit(0);
65
+ }
66
+ err(`❌ Error: ${parsed.message}`);
67
+ return exit(1);
68
+ }
69
+
70
+ function reportDryRun(result, out) {
71
+ for (const action of result.actions) {
72
+ out(` [preview] ${action}`);
73
+ }
74
+ out('\n🎉 Dry run completed. 0 files modified on disk.\n');
75
+ }
76
+
77
+ function reportSuccess(result, targetDir, out) {
78
+ out(' ✅ Progressive disclosure rules copied (docs/rules/)');
79
+ out(' ✅ Workspace knowledge hub and ADR ledger copied (docs/knowledge/, memory.md)');
80
+ out(' ✅ Specialized agentic skills copied (.agents/skills/)');
81
+ out(' ✅ Editor formatting standards initialized (.editorconfig)');
82
+ out(' ✅ Agent directives and harness symlinks established (AGENTS.md, CLAUDE.md, agents.md, GEMINI.md, .cursorrules, .windsurfrules, .github/copilot-instructions.md)');
83
+ out(' ✅ Project configuration initialized (package.json)');
84
+ if (result.gitInitialized) {
85
+ out(' ✅ Git repository initialized');
86
+ }
87
+ out('\n🎉 azcodr initialized successfully!\n');
88
+ out('Next steps:');
89
+ let step = 1;
90
+ if (targetDir !== '.' && targetDir !== './') {
91
+ out(` ${step++}. cd ${targetDir}`);
92
+ }
93
+ out(` ${step++}. Open the project in your AI coding assistant (Antigravity, Claude Code, Cursor, OpenHands)`);
94
+ out(` ${step++}. Run /lets-build to start the architectural interview and scaffold your application stack!\n`);
95
+ }
96
+
97
+ function runScaffold(resolvedTarget, parsed, io) {
98
+ const { out, err, exit, templateDir, scaffoldFn } = io;
99
+ const banner = parsed.dryRun
100
+ ? `🔍 DRY RUN: Simulating azcodr scaffolding into: ${resolvedTarget}\n`
101
+ : `📦 Scaffolding azcodr into: ${resolvedTarget}`;
102
+ if (!parsed.silent) out(banner);
103
+ try {
104
+ const result = scaffoldFn({
105
+ targetDir: resolvedTarget,
106
+ force: parsed.force,
107
+ noGit: parsed.noGit,
108
+ templateDir,
109
+ dryRun: parsed.dryRun,
110
+ silent: parsed.silent
111
+ });
112
+ if (!parsed.silent) {
113
+ if (parsed.dryRun) reportDryRun(result, out);
114
+ else reportSuccess(result, parsed.targetDir, out);
115
+ }
116
+ return exit(0);
117
+ } catch (error) {
118
+ err(`\n❌ Scaffolding failed: ${error.message}\n`);
119
+ return exit(1);
120
+ }
121
+ }
122
+
123
+ async function resolvePhase(parsed, fullIo) {
124
+ const { resolvedTarget, chosen, error } = await resolveTargetDir(parsed.targetDir, fullIo);
125
+ if (error !== null) {
126
+ fullIo.err(`❌ Error: ${error}`);
127
+ return { exitCode: 1 };
128
+ }
129
+ return { resolvedTarget, chosen };
130
+ }
131
+
132
+ async function writablePhase(target, force, fullIo) {
133
+ const writable = await ensureWritableTarget({
134
+ chosen: target.chosen,
135
+ resolvedTarget: target.resolvedTarget,
136
+ force,
137
+ io: fullIo
138
+ });
139
+ if (writable.exitCode === undefined) return { force: writable.force };
140
+ if (writable.message) {
141
+ fullIo.err(`❌ Error: ${writable.message}`);
142
+ }
143
+ return { exitCode: writable.exitCode };
144
+ }
145
+
146
+ async function runCli(rawArgs = process.argv.slice(2), io = {}) {
147
+ const fullIo = normalizeIo(io);
148
+
149
+ const parsed = parseArgs(rawArgs);
150
+ if (parsed.terminal !== null) return handleTerminal(parsed, fullIo);
151
+
152
+ if (!parsed.silent) {
153
+ outBanner(fullIo);
154
+ }
155
+
156
+ const target = await resolvePhase(parsed, fullIo);
157
+ if (target.exitCode !== undefined) return fullIo.exit(target.exitCode);
158
+
159
+ const ready = await writablePhase(target, parsed.force, fullIo);
160
+ if (ready.exitCode !== undefined) return fullIo.exit(ready.exitCode);
161
+
162
+ return runScaffold(target.resolvedTarget, { ...parsed, force: ready.force }, fullIo);
163
+ }
164
+
165
+ async function main() {
166
+ try {
167
+ await runCli(process.argv.slice(2));
168
+ } catch (err) {
169
+ console.error('Unexpected error:', err);
170
+ process.exit(1);
171
+ }
172
+ }
173
+
174
+ module.exports = {
175
+ runCli,
176
+ main,
177
+ printHelp,
178
+ printVersion,
179
+ askQuestion
180
+ };
package/lib/errors.js ADDED
@@ -0,0 +1,28 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Machine-readable failure codes.
5
+ *
6
+ * Consumers branch on `err.code`, never on message text: a wording change must
7
+ * never be a breaking change (learned from typed-settings, where `docs/errors.md`
8
+ * explicitly tells users not to regex the message). Pinned by
9
+ * tests/error-codes.test.js.
10
+ */
11
+ const ERROR_CODES = {
12
+ E_TARGET_IS_TEMPLATE: 'Cannot scaffold into the azcodr template directory itself',
13
+ E_TARGET_NOT_EMPTY: 'Target directory is not empty',
14
+ E_TARGET_IS_PROTECTED: 'Refusing to scaffold into a protected system directory',
15
+ E_GIT_ARGS_INVALID: 'runGit requires a non-empty argv array',
16
+ E_GIT_BLOCKED: 'Blocked git subcommand',
17
+ E_PATH_ESCAPE: 'Path escapes allowed root'
18
+ };
19
+
20
+ class ScaffoldError extends Error {
21
+ constructor(code, message) {
22
+ super(message);
23
+ this.name = 'ScaffoldError';
24
+ this.code = code;
25
+ }
26
+ }
27
+
28
+ module.exports = { ERROR_CODES, ScaffoldError };
package/lib/git.js ADDED
@@ -0,0 +1,29 @@
1
+ 'use strict';
2
+
3
+ const cp = require('node:child_process');
4
+ const { ScaffoldError, ERROR_CODES } = require('./errors.js');
5
+
6
+ /**
7
+ * Socket-hardened process boundary (Supply Chain: shell access).
8
+ * Scaffolder must spawn `git`, but never via a shell string.
9
+ * Uses execFileSync with argv (shell:false) and an explicit subcommand allowlist.
10
+ * No network, no env exfiltration; cwd is constrained to targetDir callers.
11
+ */
12
+ const GIT_ALLOWED_SUBCOMMANDS = new Set(['rev-parse', 'init', 'branch', 'add', 'commit']);
13
+
14
+ function runGit(args, options = {}) {
15
+ if (!Array.isArray(args) || args.length === 0) {
16
+ throw new ScaffoldError('E_GIT_ARGS_INVALID', ERROR_CODES.E_GIT_ARGS_INVALID);
17
+ }
18
+ if (!GIT_ALLOWED_SUBCOMMANDS.has(args[0])) {
19
+ throw new ScaffoldError('E_GIT_BLOCKED', `${ERROR_CODES.E_GIT_BLOCKED}: ${String(args[0])}`);
20
+ }
21
+ return cp.execFileSync('git', args, {
22
+ encoding: 'utf-8',
23
+ stdio: 'pipe',
24
+ shell: false,
25
+ ...options
26
+ });
27
+ }
28
+
29
+ module.exports = { GIT_ALLOWED_SUBCOMMANDS, runGit };
package/lib/guards.js ADDED
@@ -0,0 +1,96 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+ const os = require('node:os');
6
+ const { ScaffoldError, ERROR_CODES } = require('./errors.js');
7
+
8
+ /**
9
+ * Filesystem scope guard (Supply Chain: filesystem access).
10
+ * Constrains all reads/writes to targetDir / templateDir.
11
+ */
12
+ function assertInside(root, candidate, message) {
13
+ const resolvedRoot = path.resolve(root);
14
+ const resolvedCandidate = path.resolve(root, candidate);
15
+ const relative = path.relative(resolvedRoot, resolvedCandidate);
16
+ if (relative.startsWith('..') || path.isAbsolute(relative)) {
17
+ throw new ScaffoldError('E_PATH_ESCAPE', message || `${ERROR_CODES.E_PATH_ESCAPE}: ${candidate}`);
18
+ }
19
+ }
20
+
21
+ /**
22
+ * Normalizes a path for protected-target comparison. On Windows the filesystem
23
+ * is case-insensitive, so `C:\Users\Name` and `c:\users\name` are the same
24
+ * directory and must compare equal.
25
+ */
26
+ function normalizeForComparison(p) {
27
+ const resolved = path.resolve(p);
28
+ return process.platform === 'win32' ? resolved.toLowerCase() : resolved;
29
+ }
30
+
31
+ function readHomeDir() {
32
+ try {
33
+ return os.homedir();
34
+ } catch {
35
+ return '';
36
+ }
37
+ }
38
+
39
+ function isFilesystemRoot(resolvedTarget) {
40
+ return resolvedTarget === path.parse(resolvedTarget).root;
41
+ }
42
+
43
+ function isHomeOrParent(normalizedTarget, home) {
44
+ if (!home) return false;
45
+ if (normalizedTarget === normalizeForComparison(home)) return true;
46
+ const parent = path.dirname(path.resolve(home));
47
+ return normalizedTarget === normalizeForComparison(parent);
48
+ }
49
+
50
+ function isTemplateAncestor(resolvedTarget, templateDir) {
51
+ const resolvedTemplate = path.resolve(templateDir);
52
+ const relative = path.relative(resolvedTarget, resolvedTemplate);
53
+ return relative !== '' && !relative.startsWith('..') && !path.isAbsolute(relative);
54
+ }
55
+
56
+ function resolvesToProtectedLocation(resolvedTarget, home) {
57
+ // Symlink alias: a symlink to / or ~ must not bypass the lexical checks.
58
+ let real = null;
59
+ try {
60
+ if (!fs.existsSync(resolvedTarget)) return false;
61
+ real = fs.realpathSync(resolvedTarget);
62
+ } catch {
63
+ return false;
64
+ }
65
+ if (real === path.parse(real).root) return true;
66
+ return Boolean(home) && normalizeForComparison(real) === normalizeForComparison(home);
67
+ }
68
+
69
+ /**
70
+ * Returns true when `targetDir` is a location the scaffolder must never write
71
+ * into: the filesystem root, the user's home directory, the home directory's
72
+ * parent (e.g. /home, C:\Users -- scaffolding there affects every user), or an
73
+ * ancestor of the template itself (scaffolding into D:\projects would merge
74
+ * the template into its own parent).
75
+ *
76
+ * Lexical comparison is not enough: a symlink pointing at home must also be
77
+ * caught, so an existing target is resolved with realpath before comparing.
78
+ */
79
+ function isProtectedTarget(targetDir, options = {}) {
80
+ const resolvedTarget = path.resolve(targetDir);
81
+ const normalizedTarget = normalizeForComparison(resolvedTarget);
82
+ if (isFilesystemRoot(resolvedTarget)) return true;
83
+ const home = readHomeDir();
84
+ if (isHomeOrParent(normalizedTarget, home)) return true;
85
+ // Deferred require: scaffold.js requires this module, so a top-level
86
+ // require would be circular. Resolved at call time, after both modules load.
87
+ const { templateDir = require('./scaffold.js').getTemplateDir() } = options;
88
+ if (isTemplateAncestor(resolvedTarget, templateDir)) return true;
89
+ return resolvesToProtectedLocation(resolvedTarget, home);
90
+ }
91
+
92
+ module.exports = {
93
+ assertInside,
94
+ normalizeForComparison,
95
+ isProtectedTarget
96
+ };