create-quiver 0.9.0 → 0.10.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/README.md +312 -124
- package/README_FOR_AI.md +59 -45
- package/ROADMAP.md +12 -11
- package/docs/AI_ONBOARDING_PROMPT.md.template +120 -52
- package/docs/COMMANDS.md.template +41 -6
- package/docs/GITFLOW_PR_GUIDE.md.template +11 -0
- package/docs/STANDARD.md.template +1 -1
- package/docs/SUPPORT_MATRIX.md.template +4 -0
- package/docs/TROUBLESHOOTING.md.template +29 -1
- package/docs/WORKFLOW.md.template +1 -1
- package/package.json +6 -1
- package/package.template.json +11 -6
- package/scripts/check-pr-readiness.sh +1 -1
- package/scripts/check-scope.sh +0 -1
- package/scripts/check-slice-readiness.sh +3 -4
- package/scripts/init-docs.sh +55 -9
- package/specs/quiver-v19-self-install-dev-dep/EVIDENCE_REPORT.md +2 -2
- package/specs/quiver-v19-self-install-dev-dep/STATUS.md +4 -4
- package/specs/quiver-v19-self-install-dev-dep/slices/slice-01-auto-install-dev-dep/slice.json +4 -4
- package/specs/quiver-v20-ai-cli-orchestration/EVIDENCE_REPORT.md +23 -0
- package/specs/quiver-v20-ai-cli-orchestration/EXECUTION_PLAN.md +57 -0
- package/specs/quiver-v20-ai-cli-orchestration/SPEC.md +202 -0
- package/specs/quiver-v20-ai-cli-orchestration/STATUS.md +35 -0
- package/specs/quiver-v20-ai-cli-orchestration/pr.md +100 -0
- package/specs/quiver-v20-ai-cli-orchestration/slices/slice-00-spec-foundation/CLOSURE_BRIEF.md +30 -0
- package/specs/quiver-v20-ai-cli-orchestration/slices/slice-00-spec-foundation/EXECUTION_BRIEF.md +61 -0
- package/specs/quiver-v20-ai-cli-orchestration/slices/slice-00-spec-foundation/slice.json +54 -0
- package/specs/quiver-v20-ai-cli-orchestration/slices/slice-01-ai-provider-runner/CLOSURE_BRIEF.md +39 -0
- package/specs/quiver-v20-ai-cli-orchestration/slices/slice-01-ai-provider-runner/EXECUTION_BRIEF.md +63 -0
- package/specs/quiver-v20-ai-cli-orchestration/slices/slice-01-ai-provider-runner/slice.json +55 -0
- package/specs/quiver-v20-ai-cli-orchestration/slices/slice-02-context-packs-token-budget/CLOSURE_BRIEF.md +40 -0
- package/specs/quiver-v20-ai-cli-orchestration/slices/slice-02-context-packs-token-budget/EXECUTION_BRIEF.md +60 -0
- package/specs/quiver-v20-ai-cli-orchestration/slices/slice-02-context-packs-token-budget/slice.json +54 -0
- package/specs/quiver-v20-ai-cli-orchestration/slices/slice-03-ai-phase-gated-planner/CLOSURE_BRIEF.md +43 -0
- package/specs/quiver-v20-ai-cli-orchestration/slices/slice-03-ai-phase-gated-planner/EXECUTION_BRIEF.md +62 -0
- package/specs/quiver-v20-ai-cli-orchestration/slices/slice-03-ai-phase-gated-planner/slice.json +62 -0
- package/specs/quiver-v20-ai-cli-orchestration/slices/slice-04-spec-slice-handoff-pr-generation/CLOSURE_BRIEF.md +36 -0
- package/specs/quiver-v20-ai-cli-orchestration/slices/slice-04-spec-slice-handoff-pr-generation/EXECUTION_BRIEF.md +63 -0
- package/specs/quiver-v20-ai-cli-orchestration/slices/slice-04-spec-slice-handoff-pr-generation/slice.json +59 -0
- package/specs/quiver-v20-ai-cli-orchestration/slices/slice-05-execution-plan-parallel-worktrees/CLOSURE_BRIEF.md +32 -0
- package/specs/quiver-v20-ai-cli-orchestration/slices/slice-05-execution-plan-parallel-worktrees/EXECUTION_BRIEF.md +61 -0
- package/specs/quiver-v20-ai-cli-orchestration/slices/slice-05-execution-plan-parallel-worktrees/slice.json +59 -0
- package/specs/quiver-v20-ai-cli-orchestration/slices/slice-06-ai-execute-slice-scope-enforcement/CLOSURE_BRIEF.md +36 -0
- package/specs/quiver-v20-ai-cli-orchestration/slices/slice-06-ai-execute-slice-scope-enforcement/EXECUTION_BRIEF.md +64 -0
- package/specs/quiver-v20-ai-cli-orchestration/slices/slice-06-ai-execute-slice-scope-enforcement/slice.json +65 -0
- package/specs/quiver-v20-ai-cli-orchestration/slices/slice-07-github-pr-preflight/CLOSURE_BRIEF.md +36 -0
- package/specs/quiver-v20-ai-cli-orchestration/slices/slice-07-github-pr-preflight/EXECUTION_BRIEF.md +66 -0
- package/specs/quiver-v20-ai-cli-orchestration/slices/slice-07-github-pr-preflight/slice.json +63 -0
- package/specs/quiver-v20-ai-cli-orchestration/slices/slice-08-docs-smokes-release-readiness/CLOSURE_BRIEF.md +35 -0
- package/specs/quiver-v20-ai-cli-orchestration/slices/slice-08-docs-smokes-release-readiness/EXECUTION_BRIEF.md +64 -0
- package/specs/quiver-v20-ai-cli-orchestration/slices/slice-08-docs-smokes-release-readiness/slice.json +77 -0
- package/specs/quiver-v21-ai-first-layout/EVIDENCE_REPORT.md +31 -0
- package/specs/quiver-v21-ai-first-layout/EXECUTION_PLAN.md +185 -0
- package/specs/quiver-v21-ai-first-layout/SPEC.md +212 -0
- package/specs/quiver-v21-ai-first-layout/STATUS.md +37 -0
- package/specs/quiver-v21-ai-first-layout/pr.md +110 -0
- package/specs/quiver-v21-ai-first-layout/slices/slice-00-spec-foundation/CLOSURE_BRIEF.md +30 -0
- package/specs/quiver-v21-ai-first-layout/slices/slice-00-spec-foundation/EXECUTION_BRIEF.md +63 -0
- package/specs/quiver-v21-ai-first-layout/slices/slice-00-spec-foundation/slice.json +45 -0
- package/specs/quiver-v21-ai-first-layout/slices/slice-01-init-profiles-dry-run/CLOSURE_BRIEF.md +31 -0
- package/specs/quiver-v21-ai-first-layout/slices/slice-01-init-profiles-dry-run/EXECUTION_BRIEF.md +59 -0
- package/specs/quiver-v21-ai-first-layout/slices/slice-01-init-profiles-dry-run/slice.json +57 -0
- package/specs/quiver-v21-ai-first-layout/slices/slice-02-internal-layout-template-resolver/CLOSURE_BRIEF.md +32 -0
- package/specs/quiver-v21-ai-first-layout/slices/slice-02-internal-layout-template-resolver/EXECUTION_BRIEF.md +60 -0
- package/specs/quiver-v21-ai-first-layout/slices/slice-02-internal-layout-template-resolver/slice.json +58 -0
- package/specs/quiver-v21-ai-first-layout/slices/slice-03-generation-profiles-visible-contract/CLOSURE_BRIEF.md +34 -0
- package/specs/quiver-v21-ai-first-layout/slices/slice-03-generation-profiles-visible-contract/EXECUTION_BRIEF.md +61 -0
- package/specs/quiver-v21-ai-first-layout/slices/slice-03-generation-profiles-visible-contract/slice.json +64 -0
- package/specs/quiver-v21-ai-first-layout/slices/slice-04-analyze-scan-relocation/CLOSURE_BRIEF.md +32 -0
- package/specs/quiver-v21-ai-first-layout/slices/slice-04-analyze-scan-relocation/EXECUTION_BRIEF.md +58 -0
- package/specs/quiver-v21-ai-first-layout/slices/slice-04-analyze-scan-relocation/slice.json +64 -0
- package/specs/quiver-v21-ai-first-layout/slices/slice-05-empty-specs-layout-doctor/CLOSURE_BRIEF.md +32 -0
- package/specs/quiver-v21-ai-first-layout/slices/slice-05-empty-specs-layout-doctor/EXECUTION_BRIEF.md +60 -0
- package/specs/quiver-v21-ai-first-layout/slices/slice-05-empty-specs-layout-doctor/slice.json +65 -0
- package/specs/quiver-v21-ai-first-layout/slices/slice-06-legacy-migration-optional-assets/CLOSURE_BRIEF.md +31 -0
- package/specs/quiver-v21-ai-first-layout/slices/slice-06-legacy-migration-optional-assets/EXECUTION_BRIEF.md +62 -0
- package/specs/quiver-v21-ai-first-layout/slices/slice-06-legacy-migration-optional-assets/slice.json +66 -0
- package/specs/quiver-v21-ai-first-layout/slices/slice-07-docs-guidance-alignment/CLOSURE_BRIEF.md +33 -0
- package/specs/quiver-v21-ai-first-layout/slices/slice-07-docs-guidance-alignment/EXECUTION_BRIEF.md +61 -0
- package/specs/quiver-v21-ai-first-layout/slices/slice-07-docs-guidance-alignment/slice.json +67 -0
- package/specs/quiver-v21-ai-first-layout/slices/slice-08-smokes-release-readiness/CLOSURE_BRIEF.md +35 -0
- package/specs/quiver-v21-ai-first-layout/slices/slice-08-smokes-release-readiness/EXECUTION_BRIEF.md +66 -0
- package/specs/quiver-v21-ai-first-layout/slices/slice-08-smokes-release-readiness/slice.json +62 -0
- package/src/create-quiver/commands/ai.js +442 -0
- package/src/create-quiver/index.js +421 -84
- package/src/create-quiver/lib/ai/context-packs.js +158 -0
- package/src/create-quiver/lib/ai/execution-plan.js +254 -0
- package/src/create-quiver/lib/ai/executor.js +323 -0
- package/src/create-quiver/lib/ai/github.js +329 -0
- package/src/create-quiver/lib/ai/phase-gates.js +72 -0
- package/src/create-quiver/lib/ai/preflight.js +58 -0
- package/src/create-quiver/lib/ai/prompt-transport.js +81 -0
- package/src/create-quiver/lib/ai/prompts.js +39 -0
- package/src/create-quiver/lib/ai/providers.js +314 -0
- package/src/create-quiver/lib/ai/safety.js +151 -0
- package/src/create-quiver/lib/ai/spec-generator.js +314 -0
- package/src/create-quiver/lib/ai/spec-templates.js +715 -0
- package/src/create-quiver/lib/doctor.js +114 -0
- package/src/create-quiver/lib/git.js +21 -0
- package/src/create-quiver/lib/init-docs.js +286 -25
- package/src/create-quiver/lib/init-layout.js +426 -0
- package/src/create-quiver/lib/lifecycle.js +2 -2
- package/src/create-quiver/lib/paths.js +63 -2
- package/src/create-quiver/lib/project-scan.js +66 -0
- package/src/create-quiver/lib/readiness.js +4 -2
- package/src/create-quiver/lib/scope.js +125 -0
- package/src/create-quiver/lib/slice-graph.js +6 -0
- package/src/create-quiver/lib/slice.js +51 -8
- package/src/create-quiver/lib/state.js +18 -1
- package/src/create-quiver/lib/template-resolver.js +74 -0
- package/.claude/settings.local.json +0 -52
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
function formatError(message) {
|
|
5
|
+
return `create-quiver: ${message}`;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function toProjectSlug(projectName) {
|
|
9
|
+
return projectName
|
|
10
|
+
.normalize('NFKD')
|
|
11
|
+
.replace(/[\u0300-\u036f]/g, '')
|
|
12
|
+
.toLowerCase()
|
|
13
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
14
|
+
.replace(/^-+|-+$/g, '') || 'quiver-project';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function toRelativePath(relativePath) {
|
|
18
|
+
return relativePath.split(path.sep).join('/');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const CORE_VISIBLE_DIRECTORIES = ['docs', 'docs/ai', '.quiver', '.quiver/scans'];
|
|
22
|
+
const MINIMAL_VISIBLE_FILES = [
|
|
23
|
+
'README.md',
|
|
24
|
+
'AGENTS.md',
|
|
25
|
+
'docs/AI_CONTEXT.md',
|
|
26
|
+
'docs/AI_ONBOARDING_PROMPT.md',
|
|
27
|
+
'docs/COMMANDS.md',
|
|
28
|
+
'docs/WORKFLOW.md',
|
|
29
|
+
'docs/ai/PRINCIPLES.md',
|
|
30
|
+
'docs/ai/RULES.yaml',
|
|
31
|
+
'package.json',
|
|
32
|
+
'.quiver/state.json',
|
|
33
|
+
'.quiver/config.json',
|
|
34
|
+
'.quiver/.gitignore',
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
const DEFAULT_VISIBLE_EXTRAS = [
|
|
38
|
+
'docs/CONTEXTO.md',
|
|
39
|
+
'docs/DECISIONS.md',
|
|
40
|
+
'docs/INDEX.md',
|
|
41
|
+
'docs/STATUS.md',
|
|
42
|
+
'docs/SUPPORT_MATRIX.md',
|
|
43
|
+
'docs/TROUBLESHOOTING.md',
|
|
44
|
+
'docs/TESTING_GUIDE_FOR_AI.md',
|
|
45
|
+
'docs/GITFLOW_PR_GUIDE.md',
|
|
46
|
+
'docs/ai/LESSONS.md',
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
const FULL_VISIBLE_EXTRAS = [
|
|
50
|
+
'docs/SEARCH.md',
|
|
51
|
+
'docs/MULTI_AGENT_WORKFLOW.md',
|
|
52
|
+
'docs/MOCK_DATA_GUIDE.md',
|
|
53
|
+
'docs/UI_STANDARDS.md',
|
|
54
|
+
'docs/DOCUMENTATION_GUIDE.md',
|
|
55
|
+
'docs/examples/plan.md',
|
|
56
|
+
'docs/examples/graph.md',
|
|
57
|
+
'docs/examples/next.md',
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
const LEGACY_SCRIPT_FILES = [
|
|
61
|
+
'tools/scripts/start-slice.sh',
|
|
62
|
+
'tools/scripts/refresh-active-slices.sh',
|
|
63
|
+
'tools/scripts/check-slice-readiness.sh',
|
|
64
|
+
'tools/scripts/check-pr-readiness.sh',
|
|
65
|
+
'tools/scripts/cleanup-slice.sh',
|
|
66
|
+
'tools/scripts/check-scope.sh',
|
|
67
|
+
'tools/scripts/migrate-project.sh',
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
const FULL_DIRECTORIES = [
|
|
71
|
+
'docs-template',
|
|
72
|
+
'docs/examples',
|
|
73
|
+
'tools/scripts',
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
function quiverInternalPaths(projectRoot) {
|
|
77
|
+
const root = path.join(projectRoot, '.quiver');
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
root,
|
|
81
|
+
cacheDir: path.join(root, 'cache'),
|
|
82
|
+
configPath: path.join(root, 'config.json'),
|
|
83
|
+
gitignorePath: path.join(root, '.gitignore'),
|
|
84
|
+
runsDir: path.join(root, 'runs'),
|
|
85
|
+
scansDir: path.join(root, 'scans'),
|
|
86
|
+
statePath: path.join(root, 'state.json'),
|
|
87
|
+
templatesDir: path.join(root, 'templates'),
|
|
88
|
+
worktreesDir: path.join(root, 'worktrees'),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function buildQuiverInternalGitignore() {
|
|
93
|
+
return [
|
|
94
|
+
'cache/',
|
|
95
|
+
'runs/',
|
|
96
|
+
'worktrees/',
|
|
97
|
+
'',
|
|
98
|
+
].join('\n');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function buildQuiverConfig(options = {}) {
|
|
102
|
+
return {
|
|
103
|
+
layout_version: options.layoutVersion || 1,
|
|
104
|
+
internal_root: '.quiver',
|
|
105
|
+
scan_path: '.quiver/scans/PROJECT_SCAN.json',
|
|
106
|
+
project_map_path: 'docs/PROJECT_MAP.md',
|
|
107
|
+
templates_path: '.quiver/templates',
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function normalizeInitLayoutOptions(options = {}) {
|
|
112
|
+
const minimal = options.minimal === true;
|
|
113
|
+
const full = options.full === true;
|
|
114
|
+
|
|
115
|
+
if (minimal && full) {
|
|
116
|
+
throw new Error(formatError('init: --minimal and --full are mutually exclusive'));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
compatibilityAlias: options.compatibilityAlias === true,
|
|
121
|
+
dryRun: options.dryRun === true,
|
|
122
|
+
full,
|
|
123
|
+
includeTemplates: options.includeTemplates === true,
|
|
124
|
+
legacyScripts: options.legacyScripts === true,
|
|
125
|
+
minimal,
|
|
126
|
+
skipInstall: options.skipInstall === true,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function resolveInitProfile(normalizedOptions) {
|
|
131
|
+
if (normalizedOptions.full) {
|
|
132
|
+
return 'full';
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (normalizedOptions.minimal) {
|
|
136
|
+
return 'minimal';
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return 'default';
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function resolveInitVisibleFiles(profile, projectSlug) {
|
|
143
|
+
if (profile === 'minimal') {
|
|
144
|
+
return [...MINIMAL_VISIBLE_FILES];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (profile === 'full') {
|
|
148
|
+
return [
|
|
149
|
+
...MINIMAL_VISIBLE_FILES,
|
|
150
|
+
...DEFAULT_VISIBLE_EXTRAS,
|
|
151
|
+
...FULL_VISIBLE_EXTRAS,
|
|
152
|
+
`specs/${projectSlug}/SPEC.md`,
|
|
153
|
+
`specs/${projectSlug}/HANDOFF.md`,
|
|
154
|
+
`specs/${projectSlug}/STATUS.md`,
|
|
155
|
+
`specs/${projectSlug}/EVIDENCE_REPORT.md`,
|
|
156
|
+
`specs/${projectSlug}/slices/slice-template/slice.json`,
|
|
157
|
+
`specs/${projectSlug}/slices/slice-template/pr.md.template`,
|
|
158
|
+
];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return [...MINIMAL_VISIBLE_FILES, ...DEFAULT_VISIBLE_EXTRAS];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function resolveInitVisibleDirectories(profile, projectSlug) {
|
|
165
|
+
const directories = [...CORE_VISIBLE_DIRECTORIES];
|
|
166
|
+
|
|
167
|
+
if (profile === 'full') {
|
|
168
|
+
directories.push(...FULL_DIRECTORIES);
|
|
169
|
+
directories.push(`specs/${projectSlug}`);
|
|
170
|
+
directories.push(`specs/${projectSlug}/slices`);
|
|
171
|
+
directories.push(`specs/${projectSlug}/slices/slice-template`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return directories;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function resolveInitPackageScripts(profile, options = {}) {
|
|
178
|
+
const baseScripts = {
|
|
179
|
+
'quiver:migrate': 'npx create-quiver migrate',
|
|
180
|
+
'quiver:analyze': 'npx create-quiver analyze',
|
|
181
|
+
'quiver:plan': 'npx create-quiver plan',
|
|
182
|
+
'quiver:graph': 'npx create-quiver graph',
|
|
183
|
+
'quiver:next': 'npx create-quiver next',
|
|
184
|
+
'quiver:doctor': 'npx create-quiver doctor',
|
|
185
|
+
'quiver:ai:onboard': 'npx create-quiver ai onboard',
|
|
186
|
+
'quiver:ai:plan': 'npx create-quiver ai plan',
|
|
187
|
+
'quiver:ai:execute-slice': 'npx create-quiver ai execute-slice',
|
|
188
|
+
'quiver:ai:pr': 'npx create-quiver ai pr',
|
|
189
|
+
'quiver:ai:doctor': 'npx create-quiver ai doctor',
|
|
190
|
+
'quiver:start-slice': 'npx create-quiver start-slice',
|
|
191
|
+
'quiver:check-slice': 'npx create-quiver check-slice',
|
|
192
|
+
'quiver:check-pr': 'npx create-quiver check-pr',
|
|
193
|
+
'quiver:check-handoff': 'npx create-quiver check-handoff',
|
|
194
|
+
'check-handoff': 'npx create-quiver check-handoff',
|
|
195
|
+
'quiver:cleanup-slice': 'npx create-quiver cleanup-slice',
|
|
196
|
+
'quiver:check-scope': 'npx create-quiver check-scope',
|
|
197
|
+
'quiver:refresh-active-slices': 'npx create-quiver refresh-active-slices',
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
if (profile === 'full' || options.legacyScripts === true) {
|
|
201
|
+
return {
|
|
202
|
+
...baseScripts,
|
|
203
|
+
'check:slice': 'bash tools/scripts/check-slice-readiness.sh',
|
|
204
|
+
'check:pr': 'bash tools/scripts/check-pr-readiness.sh',
|
|
205
|
+
'start:slice': 'bash tools/scripts/start-slice.sh',
|
|
206
|
+
'cleanup:slice': 'bash tools/scripts/cleanup-slice.sh',
|
|
207
|
+
'check:scope': 'bash tools/scripts/check-scope.sh',
|
|
208
|
+
'refresh:active-slices': 'bash tools/scripts/refresh-active-slices.sh',
|
|
209
|
+
migrate: 'bash tools/scripts/migrate-project.sh',
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return baseScripts;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function pushPlannedOperation(operations, targetRoot, relativePath, kind, action, reason, profile, category) {
|
|
217
|
+
const absolutePath = path.join(targetRoot, relativePath);
|
|
218
|
+
const exists = fs.existsSync(absolutePath);
|
|
219
|
+
const effectiveAction = exists
|
|
220
|
+
? (action === 'update' ? 'update' : 'preserve')
|
|
221
|
+
: 'create';
|
|
222
|
+
|
|
223
|
+
operations.push({
|
|
224
|
+
action: effectiveAction,
|
|
225
|
+
category,
|
|
226
|
+
kind,
|
|
227
|
+
path: toRelativePath(relativePath),
|
|
228
|
+
reason,
|
|
229
|
+
exists,
|
|
230
|
+
profile,
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function pushStaticOperation(operations, relativePath, kind, action, reason, profile, category) {
|
|
235
|
+
operations.push({
|
|
236
|
+
action,
|
|
237
|
+
category,
|
|
238
|
+
kind,
|
|
239
|
+
path: toRelativePath(relativePath),
|
|
240
|
+
reason,
|
|
241
|
+
exists: false,
|
|
242
|
+
profile,
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function buildInitLayout(projectRoot, options = {}) {
|
|
247
|
+
const normalized = normalizeInitLayoutOptions(options);
|
|
248
|
+
const profile = resolveInitProfile(normalized);
|
|
249
|
+
const projectName = options.projectName || path.basename(projectRoot) || 'Quiver Project';
|
|
250
|
+
const projectSlug = toProjectSlug(projectName);
|
|
251
|
+
const operations = [];
|
|
252
|
+
const risks = [];
|
|
253
|
+
|
|
254
|
+
const visibleDirectories = resolveInitVisibleDirectories(profile, projectSlug);
|
|
255
|
+
const visibleFiles = resolveInitVisibleFiles(profile, projectSlug);
|
|
256
|
+
|
|
257
|
+
for (const directory of visibleDirectories) {
|
|
258
|
+
pushPlannedOperation(operations, projectRoot, directory, 'directory', 'create', 'core visible contract directory', profile, 'visible');
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
for (const file of visibleFiles) {
|
|
262
|
+
pushPlannedOperation(operations, projectRoot, file, 'file', file === 'package.json' ? 'update' : 'create', file === 'package.json' ? 'prepare package metadata and scripts' : 'core visible contract file', profile, 'visible');
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (profile === 'full') {
|
|
266
|
+
for (const file of LEGACY_SCRIPT_FILES) {
|
|
267
|
+
pushStaticOperation(operations, file, 'file', 'create', 'legacy Bash wrapper', profile, 'compatibility');
|
|
268
|
+
}
|
|
269
|
+
} else if (normalized.legacyScripts) {
|
|
270
|
+
for (const file of LEGACY_SCRIPT_FILES) {
|
|
271
|
+
pushStaticOperation(operations, file, 'file', 'create', 'legacy Bash wrapper', profile, 'compatibility');
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (normalized.includeTemplates) {
|
|
276
|
+
pushStaticOperation(operations, '.quiver/templates/', 'directory', 'create', 'export packaged templates into .quiver/templates', profile, 'internal');
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const ignoredPaths = [];
|
|
280
|
+
if (profile !== 'full') {
|
|
281
|
+
ignoredPaths.push('docs-template/');
|
|
282
|
+
if (!normalized.legacyScripts) {
|
|
283
|
+
ignoredPaths.push('tools/scripts/');
|
|
284
|
+
}
|
|
285
|
+
ignoredPaths.push(`specs/${projectSlug}/`);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (!normalized.includeTemplates) {
|
|
289
|
+
ignoredPaths.push('.quiver/templates/');
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (profile !== 'full') {
|
|
293
|
+
const visibleSet = new Set(visibleFiles);
|
|
294
|
+
for (const file of resolveInitVisibleFiles('full', projectSlug)) {
|
|
295
|
+
if (!visibleSet.has(file)) {
|
|
296
|
+
ignoredPaths.push(file);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (normalized.compatibilityAlias) {
|
|
302
|
+
risks.push('compatibility alias path used: npx create-quiver --name');
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (normalized.dryRun) {
|
|
306
|
+
risks.push('dry-run prints the planned layout only; the write path remains unchanged in this slice');
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const counts = operations.reduce((acc, operation) => {
|
|
310
|
+
acc[operation.action] = (acc[operation.action] || 0) + 1;
|
|
311
|
+
return acc;
|
|
312
|
+
}, {});
|
|
313
|
+
|
|
314
|
+
return {
|
|
315
|
+
profile,
|
|
316
|
+
projectName,
|
|
317
|
+
projectSlug,
|
|
318
|
+
targetRoot: path.resolve(projectRoot),
|
|
319
|
+
flags: normalized,
|
|
320
|
+
operations,
|
|
321
|
+
ignoredPaths: [...new Set(ignoredPaths)],
|
|
322
|
+
risks,
|
|
323
|
+
summary: {
|
|
324
|
+
create: counts.create || 0,
|
|
325
|
+
preserve: counts.preserve || 0,
|
|
326
|
+
update: counts.update || 0,
|
|
327
|
+
ignore: [...new Set(ignoredPaths)].length,
|
|
328
|
+
},
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function formatInitLayoutPlan(plan) {
|
|
333
|
+
const lines = [];
|
|
334
|
+
const entryPoint = plan.flags.compatibilityAlias ? 'compatibility alias (--name)' : 'explicit init command';
|
|
335
|
+
|
|
336
|
+
lines.push('Init dry-run plan');
|
|
337
|
+
lines.push(`- Project: ${plan.projectName}`);
|
|
338
|
+
lines.push(`- Slug: ${plan.projectSlug}`);
|
|
339
|
+
lines.push(`- Target: ${plan.targetRoot}`);
|
|
340
|
+
lines.push(`- Profile: ${plan.profile}`);
|
|
341
|
+
lines.push(`- Entry point: ${entryPoint}`);
|
|
342
|
+
lines.push(`- Planned create: ${plan.summary.create}`);
|
|
343
|
+
lines.push(`- Planned update: ${plan.summary.update}`);
|
|
344
|
+
lines.push(`- Planned preserve: ${plan.summary.preserve}`);
|
|
345
|
+
lines.push(`- Planned ignore: ${plan.summary.ignore}`);
|
|
346
|
+
lines.push('');
|
|
347
|
+
|
|
348
|
+
const grouped = new Map([
|
|
349
|
+
['create', []],
|
|
350
|
+
['update', []],
|
|
351
|
+
['preserve', []],
|
|
352
|
+
['ignore', plan.ignoredPaths.map((relativePath) => ({ path: relativePath }))],
|
|
353
|
+
]);
|
|
354
|
+
|
|
355
|
+
for (const operation of plan.operations) {
|
|
356
|
+
if (operation.action === 'create' || operation.action === 'update' || operation.action === 'preserve') {
|
|
357
|
+
grouped.get(operation.action).push(operation);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
lines.push('Files and directories to create');
|
|
362
|
+
if (grouped.get('create').length > 0) {
|
|
363
|
+
for (const operation of grouped.get('create')) {
|
|
364
|
+
lines.push(`- ${operation.path}`);
|
|
365
|
+
}
|
|
366
|
+
} else {
|
|
367
|
+
lines.push('- none');
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
lines.push('');
|
|
371
|
+
lines.push('Files to update');
|
|
372
|
+
if (grouped.get('update').length > 0) {
|
|
373
|
+
for (const operation of grouped.get('update')) {
|
|
374
|
+
lines.push(`- ${operation.path}`);
|
|
375
|
+
}
|
|
376
|
+
} else {
|
|
377
|
+
lines.push('- none');
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
lines.push('');
|
|
381
|
+
lines.push('Files to preserve');
|
|
382
|
+
if (grouped.get('preserve').length > 0) {
|
|
383
|
+
for (const operation of grouped.get('preserve')) {
|
|
384
|
+
lines.push(`- ${operation.path}`);
|
|
385
|
+
}
|
|
386
|
+
} else {
|
|
387
|
+
lines.push('- none');
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
lines.push('');
|
|
391
|
+
lines.push('Paths intentionally ignored by this profile');
|
|
392
|
+
if (grouped.get('ignore').length > 0) {
|
|
393
|
+
for (const operation of grouped.get('ignore')) {
|
|
394
|
+
lines.push(`- ${operation.path}`);
|
|
395
|
+
}
|
|
396
|
+
} else {
|
|
397
|
+
lines.push('- none');
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
lines.push('');
|
|
401
|
+
lines.push('Risks');
|
|
402
|
+
if (plan.risks.length > 0) {
|
|
403
|
+
for (const risk of plan.risks) {
|
|
404
|
+
lines.push(`- ${risk}`);
|
|
405
|
+
}
|
|
406
|
+
} else {
|
|
407
|
+
lines.push('- none');
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
lines.push('');
|
|
411
|
+
return lines.join('\n');
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
module.exports = {
|
|
415
|
+
buildQuiverConfig,
|
|
416
|
+
buildQuiverInternalGitignore,
|
|
417
|
+
buildInitLayout,
|
|
418
|
+
formatInitLayoutPlan,
|
|
419
|
+
normalizeInitLayoutOptions,
|
|
420
|
+
resolveInitPackageScripts,
|
|
421
|
+
resolveInitVisibleDirectories,
|
|
422
|
+
resolveInitVisibleFiles,
|
|
423
|
+
quiverInternalPaths,
|
|
424
|
+
resolveInitProfile,
|
|
425
|
+
toProjectSlug,
|
|
426
|
+
};
|
|
@@ -3,7 +3,7 @@ const path = require('path');
|
|
|
3
3
|
const { branchDelete, catFileExists, currentBranch, fetchBranch, fetchRemote, hasLocalBranch, hasRemoteBranch, lsRemoteHeads, mergeBaseIsAncestor, revListCount, runGit, statusPorcelain, worktreeAdd, worktreeList, worktreePrune, worktreeRemove } = require('./git');
|
|
4
4
|
const { parseJsonWithComments } = require('./json');
|
|
5
5
|
const { writeFrontMatter } = require('./init-docs');
|
|
6
|
-
const { resolveTargetRoot } = require('./paths');
|
|
6
|
+
const { relativePosixPath, resolveTargetRoot } = require('./paths');
|
|
7
7
|
const { activeSlicePath, renderActiveSlice, resolveSliceContext, safeBranchName, toAlias, validateSliceMetaForStart, worktreesRootForRepo } = require('./slice');
|
|
8
8
|
|
|
9
9
|
function ensureDir(dirPath) {
|
|
@@ -73,7 +73,7 @@ function writeWorktreeContext(targetWorktree, slice, branchName) {
|
|
|
73
73
|
'',
|
|
74
74
|
'## Active Slice Brief',
|
|
75
75
|
'',
|
|
76
|
-
`- ${activeSlicePath(slice.repoRoot)}`,
|
|
76
|
+
`- ${relativePosixPath(slice.repoRoot, activeSlicePath(slice.repoRoot))}`,
|
|
77
77
|
'',
|
|
78
78
|
'## Constraints',
|
|
79
79
|
'',
|
|
@@ -4,16 +4,77 @@ function resolveTargetRoot(cwd, targetDir, pathLib = path) {
|
|
|
4
4
|
return pathLib.resolve(cwd, targetDir);
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
+
function normalizeGitBashDrivePath(filePath, pathLib = path) {
|
|
8
|
+
const value = String(filePath || '');
|
|
9
|
+
const normalized = value.replace(/\\/g, '/');
|
|
10
|
+
const withoutNamespace = normalized.replace(/^\/\/\?\/([A-Za-z]:\/)/, '$1');
|
|
11
|
+
const match = withoutNamespace.match(/^\/([A-Za-z])\/(.*)$/);
|
|
12
|
+
if (!match) {
|
|
13
|
+
if (withoutNamespace !== normalized && (pathLib === path.win32 || process.platform === 'win32')) {
|
|
14
|
+
return withoutNamespace;
|
|
15
|
+
}
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (pathLib !== path.win32 && process.platform !== 'win32') {
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return `${match[1]}:/${match[2]}`;
|
|
24
|
+
}
|
|
25
|
+
|
|
7
26
|
function toPosixPath(filePath, pathLib = path) {
|
|
8
|
-
return filePath.split(pathLib.sep).join('/');
|
|
27
|
+
return String(filePath).split(pathLib.sep).join('/').replace(/\\/g, '/');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function stripTrailingSlashes(filePath) {
|
|
31
|
+
const value = String(filePath);
|
|
32
|
+
if (value === '/') {
|
|
33
|
+
return value;
|
|
34
|
+
}
|
|
35
|
+
if (/^[A-Za-z]:\/+$/.test(value)) {
|
|
36
|
+
return value.slice(0, 3);
|
|
37
|
+
}
|
|
38
|
+
return value.replace(/\/+$/, '');
|
|
9
39
|
}
|
|
10
40
|
|
|
11
41
|
function relativePosixPath(root, absolutePath, pathLib = path) {
|
|
12
|
-
|
|
42
|
+
const normalizedRoot = stripTrailingSlashes(toPosixPath(normalizeGitBashDrivePath(root, pathLib), pathLib));
|
|
43
|
+
const normalizedAbsolute = stripTrailingSlashes(toPosixPath(normalizeGitBashDrivePath(absolutePath, pathLib), pathLib));
|
|
44
|
+
const windowsPath = pathLib === path.win32 || process.platform === 'win32';
|
|
45
|
+
const comparableRoot = windowsPath ? normalizedRoot.toLowerCase() : normalizedRoot;
|
|
46
|
+
const comparableAbsolute = windowsPath ? normalizedAbsolute.toLowerCase() : normalizedAbsolute;
|
|
47
|
+
|
|
48
|
+
if (comparableAbsolute === comparableRoot) {
|
|
49
|
+
return '';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (comparableAbsolute.startsWith(`${comparableRoot}/`)) {
|
|
53
|
+
return normalizedAbsolute.slice(normalizedRoot.length + 1);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return toPosixPath(pathLib.relative(
|
|
57
|
+
normalizeGitBashDrivePath(root, pathLib),
|
|
58
|
+
normalizeGitBashDrivePath(absolutePath, pathLib),
|
|
59
|
+
), pathLib);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function specRelativePathFromPath(filePath, pathLib = path) {
|
|
63
|
+
const normalized = toPosixPath(normalizeGitBashDrivePath(filePath, pathLib), pathLib);
|
|
64
|
+
const parts = normalized.split('/').filter(Boolean);
|
|
65
|
+
const specIndex = parts.findIndex((part) => part === 'specs' || part === 'specs-fix');
|
|
66
|
+
|
|
67
|
+
if (specIndex === -1 || !parts[specIndex + 1]) {
|
|
68
|
+
return '';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return parts.slice(specIndex).join('/');
|
|
13
72
|
}
|
|
14
73
|
|
|
15
74
|
module.exports = {
|
|
75
|
+
normalizeGitBashDrivePath,
|
|
16
76
|
relativePosixPath,
|
|
17
77
|
resolveTargetRoot,
|
|
78
|
+
specRelativePathFromPath,
|
|
18
79
|
toPosixPath,
|
|
19
80
|
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
const fs = require('node:fs');
|
|
2
|
+
const path = require('node:path');
|
|
3
|
+
|
|
4
|
+
const CURRENT_SCAN_RELATIVE_PATH = '.quiver/scans/PROJECT_SCAN.json';
|
|
5
|
+
const LEGACY_SCAN_RELATIVE_PATH = 'docs/PROJECT_SCAN.json';
|
|
6
|
+
const PROJECT_MAP_RELATIVE_PATH = 'docs/PROJECT_MAP.md';
|
|
7
|
+
|
|
8
|
+
function projectScanPaths(projectRoot) {
|
|
9
|
+
return {
|
|
10
|
+
currentScanPath: path.join(projectRoot, '.quiver', 'scans', 'PROJECT_SCAN.json'),
|
|
11
|
+
legacyScanPath: path.join(projectRoot, 'docs', 'PROJECT_SCAN.json'),
|
|
12
|
+
projectMapPath: path.join(projectRoot, 'docs', 'PROJECT_MAP.md'),
|
|
13
|
+
scanDir: path.join(projectRoot, '.quiver', 'scans'),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function toRelativeScanPath(projectRoot, filePath) {
|
|
18
|
+
return path.relative(projectRoot, filePath).split(path.sep).join('/');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function writeProjectScanJson(projectRoot, scan) {
|
|
22
|
+
const { currentScanPath, scanDir } = projectScanPaths(projectRoot);
|
|
23
|
+
fs.mkdirSync(scanDir, { recursive: true });
|
|
24
|
+
fs.writeFileSync(currentScanPath, `${JSON.stringify(scan, null, 2)}\n`);
|
|
25
|
+
return currentScanPath;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function readProjectScanArtifact(projectRoot) {
|
|
29
|
+
const { currentScanPath, legacyScanPath } = projectScanPaths(projectRoot);
|
|
30
|
+
|
|
31
|
+
if (fs.existsSync(currentScanPath)) {
|
|
32
|
+
return {
|
|
33
|
+
path: currentScanPath,
|
|
34
|
+
relativePath: toRelativeScanPath(projectRoot, currentScanPath),
|
|
35
|
+
source: 'current',
|
|
36
|
+
scan: JSON.parse(fs.readFileSync(currentScanPath, 'utf8')),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (fs.existsSync(legacyScanPath)) {
|
|
41
|
+
return {
|
|
42
|
+
path: legacyScanPath,
|
|
43
|
+
relativePath: toRelativeScanPath(projectRoot, legacyScanPath),
|
|
44
|
+
source: 'legacy',
|
|
45
|
+
scan: JSON.parse(fs.readFileSync(legacyScanPath, 'utf8')),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function hasProjectScanArtifact(projectRoot) {
|
|
53
|
+
const { currentScanPath, legacyScanPath } = projectScanPaths(projectRoot);
|
|
54
|
+
return fs.existsSync(currentScanPath) || fs.existsSync(legacyScanPath);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = {
|
|
58
|
+
CURRENT_SCAN_RELATIVE_PATH,
|
|
59
|
+
LEGACY_SCAN_RELATIVE_PATH,
|
|
60
|
+
PROJECT_MAP_RELATIVE_PATH,
|
|
61
|
+
hasProjectScanArtifact,
|
|
62
|
+
projectScanPaths,
|
|
63
|
+
readProjectScanArtifact,
|
|
64
|
+
toRelativeScanPath,
|
|
65
|
+
writeProjectScanJson,
|
|
66
|
+
};
|
|
@@ -158,6 +158,8 @@ function checkSliceReadiness(sliceInput, options = {}) {
|
|
|
158
158
|
|
|
159
159
|
if (catFileExists(repoRoot, `origin/develop:${slice.sliceRel}`)) {
|
|
160
160
|
console.log('PASS: El slice ya existe en origin/develop (PR base documental mergeado).');
|
|
161
|
+
} else if (catFileExists(repoRoot, `develop:${slice.sliceRel}`)) {
|
|
162
|
+
console.log('PASS: El slice ya existe en develop local (modo sin origin).');
|
|
161
163
|
} else if (gate === 'validation') {
|
|
162
164
|
console.log('WARN: El slice no existe todavia en origin/develop. El PR base documental sigue pendiente de merge. Podes abrir el PR del slice igual — el humano mergea en orden.');
|
|
163
165
|
} else {
|
|
@@ -226,8 +228,8 @@ function checkPrReadiness(sliceInput) {
|
|
|
226
228
|
const current = currentBranch(repoRoot);
|
|
227
229
|
const prPath = path.join(path.dirname(slice.sliceAbs), 'pr.md');
|
|
228
230
|
|
|
229
|
-
checkSliceReadiness(slice.
|
|
230
|
-
checkScope(slice.
|
|
231
|
+
checkSliceReadiness(slice.sliceRel, { gate: 'validation' });
|
|
232
|
+
checkScope(slice.sliceRel, { strict: true });
|
|
231
233
|
|
|
232
234
|
if (!slice.branchName) {
|
|
233
235
|
throw new Error('create-quiver: Falta git.branch_name en el slice.');
|