@r3dlex/ai-catapult 0.1.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/LICENSE +21 -0
- package/README.md +139 -0
- package/bin/ai-catapult.js +229 -0
- package/dist/claude-plugin/.claude-plugin/marketplace.json +28 -0
- package/dist/claude-plugin/.claude-plugin/plugin.json +21 -0
- package/dist/claude-plugin/skills/ai-catapult-init/REFERENCE.md +1284 -0
- package/dist/claude-plugin/skills/ai-catapult-init/SKILL.md +79 -0
- package/dist/claude-plugin/skills/ai-catapult-init/modules/README.md +48 -0
- package/dist/claude-plugin/skills/ai-catapult-init/modules/archgate.md +42 -0
- package/dist/claude-plugin/skills/ai-catapult-init/modules/brd-prd-traceability.md +64 -0
- package/dist/claude-plugin/skills/ai-catapult-init/modules/cascade.md +110 -0
- package/dist/claude-plugin/skills/ai-catapult-init/modules/ci-policy.md +107 -0
- package/dist/claude-plugin/skills/ai-catapult-init/modules/documentation-blueprint.md +185 -0
- package/dist/claude-plugin/skills/ai-catapult-init/modules/evals.md +93 -0
- package/dist/claude-plugin/skills/ai-catapult-init/modules/foundation.md +19 -0
- package/dist/claude-plugin/skills/ai-catapult-init/modules/host-policy-automation.md +151 -0
- package/dist/claude-plugin/skills/ai-catapult-init/modules/language-packs.md +63 -0
- package/dist/claude-plugin/skills/ai-catapult-init/modules/mcp-a2a.md +63 -0
- package/dist/claude-plugin/skills/ai-catapult-init/modules/memory.md +102 -0
- package/dist/claude-plugin/skills/ai-catapult-init/modules/migration.md +107 -0
- package/dist/claude-plugin/skills/ai-catapult-init/modules/phases/01-discover-decide.md +33 -0
- package/dist/claude-plugin/skills/ai-catapult-init/modules/phases/README.md +33 -0
- package/dist/claude-plugin/skills/ai-catapult-init/modules/readme-documentation.md +120 -0
- package/dist/claude-plugin/skills/ai-catapult-init/modules/release-versioning.md +188 -0
- package/dist/claude-plugin/skills/ai-catapult-init/modules/skill-modernization.md +72 -0
- package/dist/claude-plugin/skills/ai-catapult-init/modules/sync.md +111 -0
- package/dist/claude-plugin/skills/ai-catapult-init/modules/topology.md +102 -0
- package/dist/claude-plugin/skills/ai-catapult-init/modules/traceability.md +136 -0
- package/dist/claude-plugin/skills/ai-catapult-init/modules/tracker-adapters.md +51 -0
- package/dist/claude-plugin/skills/ai-catapult-init/modules/validation.md +276 -0
- package/dist/claude-plugin/skills/ai-catapult-init/modules/workflow.md +45 -0
- package/dist/claude-plugin/skills/ai-catapult-init/templates/AGENTS.md +69 -0
- package/dist/claude-plugin/skills/ai-catapult-init/templates/CLAUDE.md +3 -0
- package/dist/claude-plugin/skills/ai-catapult-init/templates/GEMINI.md +3 -0
- package/dist/claude-plugin/skills/ai-catapult-init/templates/boundary-manifest.json +247 -0
- package/dist/claude-plugin/skills/ai-catapult-init/templates/dot-ai/drift/backups/.gitkeep +0 -0
- package/dist/claude-plugin/skills/ai-catapult-init/templates/dot-ai/drift/last-drift.json +7 -0
- package/dist/claude-plugin/skills/ai-catapult-init/templates/dot-ai/evals/.gitkeep +0 -0
- package/dist/claude-plugin/skills/ai-catapult-init/templates/dot-ai/evals/coverage-exceptions.json +1 -0
- package/dist/claude-plugin/skills/ai-catapult-init/templates/dot-ai/handoff/.gitkeep +0 -0
- package/dist/claude-plugin/skills/ai-catapult-init/templates/dot-ai/matrix.json +19 -0
- package/dist/claude-plugin/skills/ai-catapult-init/templates/dot-ai/mcp/a2a-handoff.md +51 -0
- package/dist/claude-plugin/skills/ai-catapult-init/templates/dot-ai/mcp/registry.json +27 -0
- package/dist/claude-plugin/skills/ai-catapult-init/templates/dot-ai/observability/audit-checklist.md +32 -0
- package/dist/claude-plugin/skills/ai-catapult-init/templates/dot-ai/observability/conventions.md +35 -0
- package/dist/claude-plugin/skills/ai-catapult-init/templates/dot-ai/phases/01-discover-decide/status.json +16 -0
- package/dist/claude-plugin/skills/ai-catapult-init/templates/dot-ai/phases/02-govern-plan/status.json +15 -0
- package/dist/claude-plugin/skills/ai-catapult-init/templates/dot-ai/phases/03-configure-generate/status.json +22 -0
- package/dist/claude-plugin/skills/ai-catapult-init/templates/dot-ai/phases/04-validate-handoff/status.json +18 -0
- package/dist/claude-plugin/skills/ai-catapult-init/templates/dot-ai/policies/model-routing.json +29 -0
- package/dist/claude-plugin/skills/ai-catapult-init/templates/dot-ai/reviews/ai-failure-modes.md +42 -0
- package/dist/claude-plugin/skills/ai-catapult-init/templates/dot-ai/rules/security.md +38 -0
- package/dist/claude-plugin/skills/ai-catapult-init/templates/dot-ai/rules/technical-bounds.md +38 -0
- package/dist/claude-plugin/skills/ai-catapult-init/templates/dot-ai/skills/git-ops.json +6 -0
- package/dist/claude-plugin/skills/ai-catapult-init/templates/dot-ai/skills/workspace-sync.json +6 -0
- package/dist/claude-plugin/skills/ai-catapult-init/templates/dot-ai/system-prompts/architect.md +31 -0
- package/dist/claude-plugin/skills/ai-catapult-init/templates/dot-ai/system-prompts/developer.md +31 -0
- package/dist/claude-plugin/skills/ai-catapult-init/templates/dot-ai/system-prompts/qa-engineer.md +31 -0
- package/dist/claude-plugin/skills/ai-catapult-init/templates/dot-ai/traceability/.gitkeep +0 -0
- package/dist/claude-plugin/skills/ai-catapult-init/templates/dot-ai/workflows/repo-workflow.json +42 -0
- package/dist/claude-plugin/skills/ai-catapult-init/templates/dot-ai/workflows/repo-workflow.md +52 -0
- package/dist/claude-plugin/skills/ai-catapult-init/templates/dot-github/workflows/ci-prek.yml +21 -0
- package/dist/claude-plugin/skills/ai-catapult-init/templates/dot-rules.ts +178 -0
- package/dist/claude-plugin/skills/ai-catapult-init/templates/prek.toml +13 -0
- package/dist/codex-plugin/.codex-plugin/plugin.json +11 -0
- package/dist/codex-plugin/skills/ai-catapult-init/REFERENCE.md +1284 -0
- package/dist/codex-plugin/skills/ai-catapult-init/SKILL.md +79 -0
- package/dist/codex-plugin/skills/ai-catapult-init/modules/README.md +48 -0
- package/dist/codex-plugin/skills/ai-catapult-init/modules/archgate.md +42 -0
- package/dist/codex-plugin/skills/ai-catapult-init/modules/brd-prd-traceability.md +64 -0
- package/dist/codex-plugin/skills/ai-catapult-init/modules/cascade.md +110 -0
- package/dist/codex-plugin/skills/ai-catapult-init/modules/ci-policy.md +107 -0
- package/dist/codex-plugin/skills/ai-catapult-init/modules/documentation-blueprint.md +185 -0
- package/dist/codex-plugin/skills/ai-catapult-init/modules/evals.md +93 -0
- package/dist/codex-plugin/skills/ai-catapult-init/modules/foundation.md +19 -0
- package/dist/codex-plugin/skills/ai-catapult-init/modules/host-policy-automation.md +151 -0
- package/dist/codex-plugin/skills/ai-catapult-init/modules/language-packs.md +63 -0
- package/dist/codex-plugin/skills/ai-catapult-init/modules/mcp-a2a.md +63 -0
- package/dist/codex-plugin/skills/ai-catapult-init/modules/memory.md +102 -0
- package/dist/codex-plugin/skills/ai-catapult-init/modules/migration.md +107 -0
- package/dist/codex-plugin/skills/ai-catapult-init/modules/phases/01-discover-decide.md +33 -0
- package/dist/codex-plugin/skills/ai-catapult-init/modules/phases/README.md +33 -0
- package/dist/codex-plugin/skills/ai-catapult-init/modules/readme-documentation.md +120 -0
- package/dist/codex-plugin/skills/ai-catapult-init/modules/release-versioning.md +188 -0
- package/dist/codex-plugin/skills/ai-catapult-init/modules/skill-modernization.md +72 -0
- package/dist/codex-plugin/skills/ai-catapult-init/modules/sync.md +111 -0
- package/dist/codex-plugin/skills/ai-catapult-init/modules/topology.md +102 -0
- package/dist/codex-plugin/skills/ai-catapult-init/modules/traceability.md +136 -0
- package/dist/codex-plugin/skills/ai-catapult-init/modules/tracker-adapters.md +51 -0
- package/dist/codex-plugin/skills/ai-catapult-init/modules/validation.md +276 -0
- package/dist/codex-plugin/skills/ai-catapult-init/modules/workflow.md +45 -0
- package/dist/codex-plugin/skills/ai-catapult-init/templates/AGENTS.md +69 -0
- package/dist/codex-plugin/skills/ai-catapult-init/templates/CLAUDE.md +3 -0
- package/dist/codex-plugin/skills/ai-catapult-init/templates/GEMINI.md +3 -0
- package/dist/codex-plugin/skills/ai-catapult-init/templates/boundary-manifest.json +247 -0
- package/dist/codex-plugin/skills/ai-catapult-init/templates/dot-ai/drift/backups/.gitkeep +0 -0
- package/dist/codex-plugin/skills/ai-catapult-init/templates/dot-ai/drift/last-drift.json +7 -0
- package/dist/codex-plugin/skills/ai-catapult-init/templates/dot-ai/evals/.gitkeep +0 -0
- package/dist/codex-plugin/skills/ai-catapult-init/templates/dot-ai/evals/coverage-exceptions.json +1 -0
- package/dist/codex-plugin/skills/ai-catapult-init/templates/dot-ai/handoff/.gitkeep +0 -0
- package/dist/codex-plugin/skills/ai-catapult-init/templates/dot-ai/matrix.json +19 -0
- package/dist/codex-plugin/skills/ai-catapult-init/templates/dot-ai/mcp/a2a-handoff.md +51 -0
- package/dist/codex-plugin/skills/ai-catapult-init/templates/dot-ai/mcp/registry.json +27 -0
- package/dist/codex-plugin/skills/ai-catapult-init/templates/dot-ai/observability/audit-checklist.md +32 -0
- package/dist/codex-plugin/skills/ai-catapult-init/templates/dot-ai/observability/conventions.md +35 -0
- package/dist/codex-plugin/skills/ai-catapult-init/templates/dot-ai/phases/01-discover-decide/status.json +16 -0
- package/dist/codex-plugin/skills/ai-catapult-init/templates/dot-ai/phases/02-govern-plan/status.json +15 -0
- package/dist/codex-plugin/skills/ai-catapult-init/templates/dot-ai/phases/03-configure-generate/status.json +22 -0
- package/dist/codex-plugin/skills/ai-catapult-init/templates/dot-ai/phases/04-validate-handoff/status.json +18 -0
- package/dist/codex-plugin/skills/ai-catapult-init/templates/dot-ai/policies/model-routing.json +29 -0
- package/dist/codex-plugin/skills/ai-catapult-init/templates/dot-ai/reviews/ai-failure-modes.md +42 -0
- package/dist/codex-plugin/skills/ai-catapult-init/templates/dot-ai/rules/security.md +38 -0
- package/dist/codex-plugin/skills/ai-catapult-init/templates/dot-ai/rules/technical-bounds.md +38 -0
- package/dist/codex-plugin/skills/ai-catapult-init/templates/dot-ai/skills/git-ops.json +6 -0
- package/dist/codex-plugin/skills/ai-catapult-init/templates/dot-ai/skills/workspace-sync.json +6 -0
- package/dist/codex-plugin/skills/ai-catapult-init/templates/dot-ai/system-prompts/architect.md +31 -0
- package/dist/codex-plugin/skills/ai-catapult-init/templates/dot-ai/system-prompts/developer.md +31 -0
- package/dist/codex-plugin/skills/ai-catapult-init/templates/dot-ai/system-prompts/qa-engineer.md +31 -0
- package/dist/codex-plugin/skills/ai-catapult-init/templates/dot-ai/traceability/.gitkeep +0 -0
- package/dist/codex-plugin/skills/ai-catapult-init/templates/dot-ai/workflows/repo-workflow.json +42 -0
- package/dist/codex-plugin/skills/ai-catapult-init/templates/dot-ai/workflows/repo-workflow.md +52 -0
- package/dist/codex-plugin/skills/ai-catapult-init/templates/dot-github/workflows/ci-prek.yml +21 -0
- package/dist/codex-plugin/skills/ai-catapult-init/templates/dot-rules.ts +178 -0
- package/dist/codex-plugin/skills/ai-catapult-init/templates/prek.toml +13 -0
- package/package.json +51 -0
- package/scripts/build-claude-plugin.sh +179 -0
- package/scripts/build-codex-plugin.sh +104 -0
- package/scripts/snapshot-dist.sh +26 -0
- package/setup.sh +63 -0
- package/skills.lock.json +6 -0
- package/src/install.js +380 -0
- package/src/scaffold.js +220 -0
package/src/scaffold.js
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* scaffold.js — deterministic v3 .ai/ scaffold engine (Slice 3).
|
|
3
|
+
*
|
|
4
|
+
* Reads vendored ai-catapult-init/templates/ + boundary-manifest.json and
|
|
5
|
+
* emits all MECHANICAL paths into a target directory. Judgment-laden paths
|
|
6
|
+
* are not written as files (only their parent dirs may be created where a
|
|
7
|
+
* .gitkeep sits in the template tree to track the directory).
|
|
8
|
+
*
|
|
9
|
+
* Determinism guarantees:
|
|
10
|
+
* - Template files are iterated in sorted manifest order (not filesystem order).
|
|
11
|
+
* - Token substitution is a pure synchronous replace — no timestamps beyond
|
|
12
|
+
* the injected {{DATE}}, no Math.random, no OS entropy.
|
|
13
|
+
* - Same inputs → byte-identical output.
|
|
14
|
+
*
|
|
15
|
+
* Token map (all tokens that appear across templates):
|
|
16
|
+
* {{REPO_ID}} → repoId
|
|
17
|
+
* {{DATE}} → date (YYYY-MM-DD)
|
|
18
|
+
* {{UPSTREAM_URL}} → upstreamUrl
|
|
19
|
+
* {{UPSTREAM_REF}} → upstreamRef
|
|
20
|
+
*
|
|
21
|
+
* Path-prefix mapping (template path → real filesystem path):
|
|
22
|
+
* dot-ai/ → .ai/
|
|
23
|
+
* dot-github/ → .github/
|
|
24
|
+
* dot-rules.ts → .rules.ts
|
|
25
|
+
* (everything else maps 1:1)
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { readFileSync, writeFileSync, mkdirSync, readdirSync, existsSync } from 'node:fs';
|
|
29
|
+
import { dirname, join, resolve, sep } from 'node:path';
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Map a template-relative path to its real output path.
|
|
33
|
+
* e.g. "dot-ai/matrix.json" → ".ai/matrix.json"
|
|
34
|
+
* "dot-github/workflows/ci.yml" → ".github/workflows/ci.yml"
|
|
35
|
+
* "dot-rules.ts" → ".rules.ts"
|
|
36
|
+
*/
|
|
37
|
+
function templatePathToRealPath(templatePath) {
|
|
38
|
+
if (templatePath.startsWith('dot-ai/')) {
|
|
39
|
+
return '.ai/' + templatePath.slice('dot-ai/'.length);
|
|
40
|
+
}
|
|
41
|
+
if (templatePath.startsWith('dot-github/')) {
|
|
42
|
+
return '.github/' + templatePath.slice('dot-github/'.length);
|
|
43
|
+
}
|
|
44
|
+
if (templatePath === 'dot-rules.ts') {
|
|
45
|
+
return '.rules.ts';
|
|
46
|
+
}
|
|
47
|
+
// All other templates (AGENTS.md, CLAUDE.md, GEMINI.md, prek.toml) map 1:1.
|
|
48
|
+
return templatePath;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Substitute all {{TOKEN}} placeholders in content.
|
|
53
|
+
* @param {string} content - raw template content
|
|
54
|
+
* @param {object} tokens - { REPO_ID, DATE, UPSTREAM_URL, UPSTREAM_REF }
|
|
55
|
+
* @returns {string}
|
|
56
|
+
*/
|
|
57
|
+
function substituteTokens(content, tokens) {
|
|
58
|
+
return content
|
|
59
|
+
.replaceAll('{{REPO_ID}}', tokens.REPO_ID)
|
|
60
|
+
.replaceAll('{{DATE}}', tokens.DATE)
|
|
61
|
+
.replaceAll('{{UPSTREAM_URL}}', tokens.UPSTREAM_URL)
|
|
62
|
+
.replaceAll('{{UPSTREAM_REF}}', tokens.UPSTREAM_REF);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Assert that destPath is strictly inside targetDir.
|
|
67
|
+
* Throws a clear error if a path traversal is detected.
|
|
68
|
+
* @param {string} destPath - absolute destination path
|
|
69
|
+
* @param {string} targetDir - absolute target root
|
|
70
|
+
*/
|
|
71
|
+
function assertNoTraversal(destPath, targetDir) {
|
|
72
|
+
const resolvedDest = resolve(destPath);
|
|
73
|
+
const resolvedRoot = resolve(targetDir) + sep;
|
|
74
|
+
if (!resolvedDest.startsWith(resolvedRoot)) {
|
|
75
|
+
throw new Error(
|
|
76
|
+
`Path traversal detected: manifest path resolves to "${resolvedDest}" which is outside target directory "${resolve(targetDir)}". Aborting.`,
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Recursively copy any .gitkeep files from the template tree into targetDir,
|
|
83
|
+
* using the same dot-* path mapping as templatePathToRealPath.
|
|
84
|
+
*
|
|
85
|
+
* @param {string} baseTemplatesDir - absolute path to templates root (for computing relative paths)
|
|
86
|
+
* @param {string} currentDir - current directory being walked
|
|
87
|
+
* @param {string} targetDir - absolute output root
|
|
88
|
+
* @param {boolean} force - overwrite existing files
|
|
89
|
+
* @param {string[]} collisions - accumulator for collision paths (when force=false)
|
|
90
|
+
*/
|
|
91
|
+
function emitGitkeeps(baseTemplatesDir, currentDir, targetDir, force, collisions) {
|
|
92
|
+
const entries = readdirSync(currentDir, { withFileTypes: true });
|
|
93
|
+
// Sort for determinism
|
|
94
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
95
|
+
for (const entry of entries) {
|
|
96
|
+
const srcFull = join(currentDir, entry.name);
|
|
97
|
+
if (entry.isDirectory()) {
|
|
98
|
+
emitGitkeeps(baseTemplatesDir, srcFull, targetDir, force, collisions);
|
|
99
|
+
} else if (entry.name === '.gitkeep') {
|
|
100
|
+
// e.g. baseTemplatesDir = "/…/templates", srcFull = "/…/templates/dot-ai/evals/.gitkeep"
|
|
101
|
+
// relFromTemplates = "dot-ai/evals/.gitkeep"
|
|
102
|
+
const relFromTemplates = srcFull.slice(baseTemplatesDir.length + 1);
|
|
103
|
+
const realRel = templatePathToRealPath(relFromTemplates);
|
|
104
|
+
const destFull = join(targetDir, realRel);
|
|
105
|
+
assertNoTraversal(destFull, targetDir);
|
|
106
|
+
if (!force && existsSync(destFull)) {
|
|
107
|
+
collisions.push(realRel);
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
mkdirSync(dirname(destFull), { recursive: true });
|
|
111
|
+
writeFileSync(destFull, '', 'utf8');
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Scaffold the mechanical v3 .ai/ skeleton into targetDir.
|
|
118
|
+
*
|
|
119
|
+
* @param {object} opts
|
|
120
|
+
* @param {string} opts.targetDir - absolute path to emit into
|
|
121
|
+
* @param {string} opts.templatesDir - absolute path to vendored templates/
|
|
122
|
+
* @param {string} opts.repoId - {{REPO_ID}} substitution value
|
|
123
|
+
* @param {string} opts.date - {{DATE}} substitution value (YYYY-MM-DD)
|
|
124
|
+
* @param {string} opts.upstreamUrl - {{UPSTREAM_URL}} substitution value
|
|
125
|
+
* @param {string} opts.upstreamRef - {{UPSTREAM_REF}} substitution value
|
|
126
|
+
* @param {boolean} [opts.force] - overwrite existing files without error
|
|
127
|
+
* @returns {{ emittedPaths: string[], judgmentLadenPaths: string[] }}
|
|
128
|
+
*/
|
|
129
|
+
export function scaffold({ targetDir, templatesDir, repoId, date, upstreamUrl, upstreamRef, force = false }) {
|
|
130
|
+
const manifestPath = join(templatesDir, 'boundary-manifest.json');
|
|
131
|
+
|
|
132
|
+
// Fix #8: friendly error when vendor/manifest is missing
|
|
133
|
+
let manifest;
|
|
134
|
+
try {
|
|
135
|
+
manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
136
|
+
} catch (err) {
|
|
137
|
+
if (err.code === 'ENOENT') {
|
|
138
|
+
process.stderr.write('vendor/ missing or stale — run: bash setup.sh\n');
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
throw err;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const tokens = {
|
|
145
|
+
REPO_ID: repoId,
|
|
146
|
+
DATE: date,
|
|
147
|
+
UPSTREAM_URL: upstreamUrl,
|
|
148
|
+
UPSTREAM_REF: upstreamRef,
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
// Fix #3: collect collisions before writing anything
|
|
152
|
+
const collisions = [];
|
|
153
|
+
|
|
154
|
+
// Iterate in manifest order (deterministic — not filesystem readdir order).
|
|
155
|
+
for (const entry of manifest.paths) {
|
|
156
|
+
if (entry.classification !== 'mechanical' || entry.template === null) {
|
|
157
|
+
// Judgment-laden: do not emit any file.
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const templateRelPath = entry.template; // e.g. "dot-ai/matrix.json"
|
|
162
|
+
const realRelPath = templatePathToRealPath(templateRelPath); // e.g. ".ai/matrix.json"
|
|
163
|
+
|
|
164
|
+
const srcPath = join(templatesDir, templateRelPath);
|
|
165
|
+
const destPath = join(targetDir, realRelPath);
|
|
166
|
+
|
|
167
|
+
// Fix #4: defense-in-depth traversal check
|
|
168
|
+
assertNoTraversal(destPath, targetDir);
|
|
169
|
+
|
|
170
|
+
if (!force && existsSync(destPath)) {
|
|
171
|
+
collisions.push(realRelPath);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Fix #3: refuse if collisions exist and --force not passed
|
|
176
|
+
if (collisions.length > 0) {
|
|
177
|
+
process.stderr.write(
|
|
178
|
+
`error: init would overwrite existing files in ${targetDir}:\n` +
|
|
179
|
+
collisions.map((p) => ` ${p}`).join('\n') +
|
|
180
|
+
'\nPass --force to overwrite.\n',
|
|
181
|
+
);
|
|
182
|
+
process.exit(1);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// All clear — write files
|
|
186
|
+
const emittedPaths = [];
|
|
187
|
+
for (const entry of manifest.paths) {
|
|
188
|
+
if (entry.classification !== 'mechanical' || entry.template === null) {
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const templateRelPath = entry.template;
|
|
193
|
+
const realRelPath = templatePathToRealPath(templateRelPath);
|
|
194
|
+
|
|
195
|
+
const srcPath = join(templatesDir, templateRelPath);
|
|
196
|
+
const destPath = join(targetDir, realRelPath);
|
|
197
|
+
|
|
198
|
+
const raw = readFileSync(srcPath, 'utf8');
|
|
199
|
+
const rendered = substituteTokens(raw, tokens);
|
|
200
|
+
|
|
201
|
+
mkdirSync(dirname(destPath), { recursive: true });
|
|
202
|
+
writeFileSync(destPath, rendered, 'utf8');
|
|
203
|
+
emittedPaths.push(realRelPath);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Emit .gitkeep files so tracked empty directories land in the target.
|
|
207
|
+
// These are not listed in the boundary-manifest (they are git artifacts),
|
|
208
|
+
// so we walk the template tree for .gitkeep files and mirror them verbatim.
|
|
209
|
+
const gitkeepCollisions = [];
|
|
210
|
+
emitGitkeeps(templatesDir, templatesDir, targetDir, force, gitkeepCollisions);
|
|
211
|
+
// .gitkeep collisions are non-fatal — they are empty marker files; silently
|
|
212
|
+
// skip them if --force was not given (the directory already exists).
|
|
213
|
+
|
|
214
|
+
// Collect judgment-laden paths from manifest (for finish prompt).
|
|
215
|
+
const judgmentLadenPaths = manifest.paths
|
|
216
|
+
.filter((e) => e.classification === 'judgment_laden')
|
|
217
|
+
.map((e) => e.path);
|
|
218
|
+
|
|
219
|
+
return { emittedPaths, judgmentLadenPaths };
|
|
220
|
+
}
|