create-quiver 0.12.0 → 0.13.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/CHANGELOG.md +52 -0
- package/README.md +65 -25
- package/README_FOR_AI.md +36 -29
- package/ROADMAP.md +22 -3
- package/docs/AI_ONBOARDING_PROMPT.md.template +7 -1
- package/docs/COMMANDS.md.template +53 -20
- package/docs/STATUS.md.template +5 -1
- package/docs/WORKFLOW.md.template +13 -11
- package/package.json +10 -3
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/EVIDENCE_REPORT.md +293 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/EXECUTION_PLAN.md +58 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/SPEC.md +242 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/STATUS.md +35 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/pr.md +77 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-00-spec-foundation/CLOSURE_BRIEF.md +34 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-00-spec-foundation/EXECUTION_BRIEF.md +52 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-00-spec-foundation/slice.json +52 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-01-cli-contract-compatibility/CLOSURE_BRIEF.md +36 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-01-cli-contract-compatibility/EXECUTION_BRIEF.md +52 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-01-cli-contract-compatibility/slice.json +56 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-02-run-state-phase-locks/CLOSURE_BRIEF.md +43 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-02-run-state-phase-locks/EXECUTION_BRIEF.md +54 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-02-run-state-phase-locks/slice.json +52 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-03-safe-ai-onboarding-docs/CLOSURE_BRIEF.md +35 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-03-safe-ai-onboarding-docs/EXECUTION_BRIEF.md +53 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-03-safe-ai-onboarding-docs/slice.json +54 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-04-agent-profiles-adapters/CLOSURE_BRIEF.md +34 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-04-agent-profiles-adapters/EXECUTION_BRIEF.md +54 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-04-agent-profiles-adapters/slice.json +52 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-05-approval-gates/CLOSURE_BRIEF.md +34 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-05-approval-gates/EXECUTION_BRIEF.md +54 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-05-approval-gates/slice.json +53 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-06-spec-slice-generator/CLOSURE_BRIEF.md +33 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-06-spec-slice-generator/EXECUTION_BRIEF.md +56 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-06-spec-slice-generator/slice.json +55 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-07-slice-execution-planner/CLOSURE_BRIEF.md +33 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-07-slice-execution-planner/EXECUTION_BRIEF.md +54 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-07-slice-execution-planner/slice.json +52 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-08-controlled-slice-execution/CLOSURE_BRIEF.md +39 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-08-controlled-slice-execution/EXECUTION_BRIEF.md +56 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-08-controlled-slice-execution/slice.json +53 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-09-git-worktree-pr-lifecycle/CLOSURE_BRIEF.md +38 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-09-git-worktree-pr-lifecycle/EXECUTION_BRIEF.md +57 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-09-git-worktree-pr-lifecycle/slice.json +52 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-10-validation-errors-fixtures/CLOSURE_BRIEF.md +39 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-10-validation-errors-fixtures/EXECUTION_BRIEF.md +55 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-10-validation-errors-fixtures/slice.json +56 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-11-export-dashboard-migration/CLOSURE_BRIEF.md +36 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-11-export-dashboard-migration/EXECUTION_BRIEF.md +54 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-11-export-dashboard-migration/slice.json +53 -0
- package/specs/quiver-v26-0121-smoke-hardening/EVIDENCE_REPORT.md +208 -0
- package/specs/quiver-v26-0121-smoke-hardening/EXECUTION_PLAN.md +57 -0
- package/specs/quiver-v26-0121-smoke-hardening/SPEC.md +137 -0
- package/specs/quiver-v26-0121-smoke-hardening/STATUS.md +32 -0
- package/specs/quiver-v26-0121-smoke-hardening/pr.md +96 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-00-docs-foundation/CLOSURE_BRIEF.md +35 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-00-docs-foundation/EXECUTION_BRIEF.md +55 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-00-docs-foundation/slice.json +73 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-01-cli-help-version-contract/CLOSURE_BRIEF.md +38 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-01-cli-help-version-contract/EXECUTION_BRIEF.md +51 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-01-cli-help-version-contract/slice.json +76 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-02-init-doc-links-and-flow-guidance/CLOSURE_BRIEF.md +37 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-02-init-doc-links-and-flow-guidance/EXECUTION_BRIEF.md +52 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-02-init-doc-links-and-flow-guidance/slice.json +75 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-03-ai-approval-review-consistency/CLOSURE_BRIEF.md +37 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-03-ai-approval-review-consistency/EXECUTION_BRIEF.md +53 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-03-ai-approval-review-consistency/slice.json +77 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-04-local-validation-brief-contracts/CLOSURE_BRIEF.md +35 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-04-local-validation-brief-contracts/EXECUTION_BRIEF.md +52 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-04-local-validation-brief-contracts/slice.json +77 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-05-demo-scaffold-readiness/CLOSURE_BRIEF.md +34 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-05-demo-scaffold-readiness/EXECUTION_BRIEF.md +54 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-05-demo-scaffold-readiness/slice.json +84 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-06-plan-graph-scope-performance/CLOSURE_BRIEF.md +35 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-06-plan-graph-scope-performance/EXECUTION_BRIEF.md +53 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-06-plan-graph-scope-performance/slice.json +82 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-07-smoke-release-readiness/CLOSURE_BRIEF.md +35 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-07-smoke-release-readiness/EXECUTION_BRIEF.md +55 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-07-smoke-release-readiness/slice.json +92 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/AUDIT_V24_V25_V26.md +67 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/COMMAND_CONTRACTS.md +125 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/COVERAGE_MATRIX.md +74 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/EVIDENCE_REPORT.md +179 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/EXECUTION_PLAN.md +71 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/SPEC.md +176 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/STATUS.md +37 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/pr.md +132 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-00-docs-audit-coverage-and-contracts/CLOSURE_BRIEF.md +36 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-00-docs-audit-coverage-and-contracts/EXECUTION_BRIEF.md +56 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-00-docs-audit-coverage-and-contracts/slice.json +75 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-01-core-state-resolver-and-canonical-statuses/CLOSURE_BRIEF.md +37 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-01-core-state-resolver-and-canonical-statuses/EXECUTION_BRIEF.md +54 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-01-core-state-resolver-and-canonical-statuses/slice.json +79 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-02-json-export-contract-and-machine-output/CLOSURE_BRIEF.md +34 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-02-json-export-contract-and-machine-output/EXECUTION_BRIEF.md +54 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-02-json-export-contract-and-machine-output/slice.json +75 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-03-approved-plan-to-spec-create/CLOSURE_BRIEF.md +36 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-03-approved-plan-to-spec-create/EXECUTION_BRIEF.md +55 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-03-approved-plan-to-spec-create/slice.json +78 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-04-ai-artifact-storage-redaction-and-token-compaction/CLOSURE_BRIEF.md +31 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-04-ai-artifact-storage-redaction-and-token-compaction/EXECUTION_BRIEF.md +55 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-04-ai-artifact-storage-redaction-and-token-compaction/slice.json +77 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-05-worktree-lifecycle-locks-and-recovery/CLOSURE_BRIEF.md +31 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-05-worktree-lifecycle-locks-and-recovery/EXECUTION_BRIEF.md +55 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-05-worktree-lifecycle-locks-and-recovery/slice.json +84 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-06-validation-gates-and-scope-safety/CLOSURE_BRIEF.md +32 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-06-validation-gates-and-scope-safety/EXECUTION_BRIEF.md +57 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-06-validation-gates-and-scope-safety/slice.json +99 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-07-context-analysis-and-doctor-flow/CLOSURE_BRIEF.md +31 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-07-context-analysis-and-doctor-flow/EXECUTION_BRIEF.md +57 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-07-context-analysis-and-doctor-flow/slice.json +88 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-08-cross-platform-help-auth-and-dx/CLOSURE_BRIEF.md +31 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-08-cross-platform-help-auth-and-dx/EXECUTION_BRIEF.md +56 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-08-cross-platform-help-auth-and-dx/slice.json +85 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-09-fixtures-smoke-docs-and-release-readiness/CLOSURE_BRIEF.md +32 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-09-fixtures-smoke-docs-and-release-readiness/EXECUTION_BRIEF.md +56 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-09-fixtures-smoke-docs-and-release-readiness/slice.json +91 -0
- package/src/create-quiver/commands/ai.js +652 -27
- package/src/create-quiver/commands/flow.js +58 -9
- package/src/create-quiver/commands/graph.js +11 -9
- package/src/create-quiver/commands/plan.js +7 -16
- package/src/create-quiver/commands/spec.js +282 -0
- package/src/create-quiver/index.js +409 -31
- package/src/create-quiver/lib/actionable-error.js +27 -0
- package/src/create-quiver/lib/agent-profiles.js +16 -4
- package/src/create-quiver/lib/ai/artifacts.js +318 -0
- package/src/create-quiver/lib/ai/context-packs.js +4 -0
- package/src/create-quiver/lib/ai/execution-plan.js +16 -1
- package/src/create-quiver/lib/ai/executor.js +272 -21
- package/src/create-quiver/lib/ai/export-state.js +679 -0
- package/src/create-quiver/lib/ai/github.js +162 -2
- package/src/create-quiver/lib/ai/onboarding-template.js +215 -2
- package/src/create-quiver/lib/ai/plan-review.js +7 -2
- package/src/create-quiver/lib/ai/providers.js +4 -3
- package/src/create-quiver/lib/ai/run-state.js +414 -0
- package/src/create-quiver/lib/ai/spec-generator.js +84 -13
- package/src/create-quiver/lib/ai/spec-templates.js +150 -21
- package/src/create-quiver/lib/analyze.js +2 -2
- package/src/create-quiver/lib/approvals.js +36 -5
- package/src/create-quiver/lib/demo.js +189 -14
- package/src/create-quiver/lib/doctor.js +154 -0
- package/src/create-quiver/lib/git.js +40 -1
- package/src/create-quiver/lib/handoff.js +123 -12
- package/src/create-quiver/lib/init-docs.js +35 -13
- package/src/create-quiver/lib/init-layout.js +9 -0
- package/src/create-quiver/lib/json.js +53 -3
- package/src/create-quiver/lib/lifecycle.js +52 -3
- package/src/create-quiver/lib/locks.js +134 -0
- package/src/create-quiver/lib/package-safety.js +7 -0
- package/src/create-quiver/lib/paths.js +74 -0
- package/src/create-quiver/lib/project-scan.js +74 -0
- package/src/create-quiver/lib/project-state-resolver.js +236 -0
- package/src/create-quiver/lib/readiness.js +66 -10
- package/src/create-quiver/lib/scope.js +52 -8
- package/src/create-quiver/lib/slice-graph.js +138 -38
- package/src/create-quiver/lib/slice.js +14 -5
- package/src/create-quiver/lib/spec-worktrees.js +129 -32
- package/src/create-quiver/lib/statuses.js +115 -0
|
@@ -2,8 +2,9 @@ const fs = require('fs');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const { catFileExists, currentBranch, hasLocalBranch, hasRemoteBranch, mergeBaseIsAncestor, revListCount, runGit, statusPorcelain, worktreeList } = require('./git');
|
|
4
4
|
const { parseJsonWithComments } = require('./json');
|
|
5
|
-
const { buildGraph, readAllSlices, SliceGraphError, topoSort } = require('./slice-graph');
|
|
6
|
-
const { resolveSliceContext, toAlias } = require('./slice');
|
|
5
|
+
const { buildGraph, normalizeDeclaredDependencies, readAllSlices, SliceGraphError, topoSort } = require('./slice-graph');
|
|
6
|
+
const { resolveSliceContext, toAlias, validateSliceMetaForStart } = require('./slice');
|
|
7
|
+
const { validateProjectRelativePaths } = require('./paths');
|
|
7
8
|
|
|
8
9
|
function ensureExists(filePath, message) {
|
|
9
10
|
if (!fs.existsSync(filePath)) {
|
|
@@ -111,12 +112,30 @@ function validateLocalSliceArtifacts(repoRoot, slice) {
|
|
|
111
112
|
throw new Error('create-quiver: slice.json.files contiene entradas invalidas.');
|
|
112
113
|
}
|
|
113
114
|
console.log('PASS: slice.json declara archivos de alcance.');
|
|
115
|
+
|
|
116
|
+
validateSliceMetaForStart(slice);
|
|
117
|
+
console.log('PASS: slice.json declara metadata git compatible con start-slice.');
|
|
118
|
+
|
|
119
|
+
validateProjectRelativePaths(slice.files, 'slice.json files/allowed_write_paths');
|
|
120
|
+
validateProjectRelativePaths(slice.expectedReadPaths, 'slice.json expected_read_paths');
|
|
121
|
+
console.log('PASS: slice.json declara rutas relativas seguras dentro del proyecto.');
|
|
114
122
|
}
|
|
115
123
|
|
|
116
124
|
function baseRecoveryMessage(remote, baseBranch) {
|
|
117
125
|
return `No se encontro la base '${baseBranch}' como rama local ni como '${remote}/${baseBranch}'. Para validacion estructural usa --local; para validacion contra otra base usa --base <branch>; o configura/fetchea el remoto '${remote}'.`;
|
|
118
126
|
}
|
|
119
127
|
|
|
128
|
+
function resolveReadinessRoot(localMode) {
|
|
129
|
+
try {
|
|
130
|
+
return runGit(['rev-parse', '--show-toplevel'], process.cwd());
|
|
131
|
+
} catch (error) {
|
|
132
|
+
if (localMode) {
|
|
133
|
+
return process.cwd();
|
|
134
|
+
}
|
|
135
|
+
throw error;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
120
139
|
function validateSliceDocumentedOnBase(repoRoot, slice, options = {}) {
|
|
121
140
|
const gate = options.gate || 'execution';
|
|
122
141
|
const remote = options.remote || 'origin';
|
|
@@ -178,8 +197,12 @@ function validateDeclaredDependencyContract(repoRoot, slice) {
|
|
|
178
197
|
throw new Error(`create-quiver: depends_on contiene referencias duplicadas en ${currentRef}.`);
|
|
179
198
|
}
|
|
180
199
|
|
|
200
|
+
const normalizedDeclared = normalizeDeclaredDependencies(currentNode, declared);
|
|
201
|
+
if (normalizedDeclared.length !== new Set(normalizedDeclared).size) {
|
|
202
|
+
throw new Error(`create-quiver: depends_on contiene referencias duplicadas en ${currentRef}.`);
|
|
203
|
+
}
|
|
181
204
|
const currentSet = new Set(currentNode.depends_on || []);
|
|
182
|
-
for (const dep of
|
|
205
|
+
for (const dep of normalizedDeclared) {
|
|
183
206
|
if (!currentSet.has(dep)) {
|
|
184
207
|
throw new Error(`create-quiver: depends_on apunta a una referencia inexistente o invalida: ${dep}`);
|
|
185
208
|
}
|
|
@@ -204,12 +227,17 @@ function validateDeclaredDependencyContract(repoRoot, slice) {
|
|
|
204
227
|
}
|
|
205
228
|
}
|
|
206
229
|
|
|
230
|
+
function localCheckSummary() {
|
|
231
|
+
console.log('INFO: Modo local: checks ejecutados: spec docs, briefs, metadata git, scope declarado, rutas seguras, dependencias y gate.');
|
|
232
|
+
console.log('INFO: Modo local: checks omitidos: existencia en base remota/local y overlap contra worktrees activos.');
|
|
233
|
+
}
|
|
234
|
+
|
|
207
235
|
function checkSliceReadiness(sliceInput, options = {}) {
|
|
208
236
|
const gate = options.gate || 'execution';
|
|
209
237
|
const localMode = options.local === true;
|
|
210
238
|
const strictOverlap = options.strictOverlap === true;
|
|
211
239
|
const remote = options.remote || 'origin';
|
|
212
|
-
const repoRoot =
|
|
240
|
+
const repoRoot = resolveReadinessRoot(localMode);
|
|
213
241
|
const slice = resolveSliceContext(repoRoot, sliceInput);
|
|
214
242
|
const baseBranch = options.baseBranch || slice.baseBranch || 'develop';
|
|
215
243
|
|
|
@@ -247,6 +275,9 @@ function checkSliceReadiness(sliceInput, options = {}) {
|
|
|
247
275
|
}
|
|
248
276
|
|
|
249
277
|
validateDeclaredDependencyContract(repoRoot, slice);
|
|
278
|
+
if (localMode) {
|
|
279
|
+
localCheckSummary();
|
|
280
|
+
}
|
|
250
281
|
|
|
251
282
|
switch (gate) {
|
|
252
283
|
case 'ready':
|
|
@@ -356,22 +387,47 @@ function checkPrReadiness(sliceInput) {
|
|
|
356
387
|
|
|
357
388
|
function checkScope(sliceInput, options = {}) {
|
|
358
389
|
const strict = options.strict === true;
|
|
390
|
+
const remote = options.remote || 'origin';
|
|
359
391
|
const repoRoot = runGit(['rev-parse', '--show-toplevel'], process.cwd());
|
|
360
392
|
const slice = resolveSliceContext(repoRoot, sliceInput);
|
|
361
393
|
const declared = slice.files;
|
|
394
|
+
validateProjectRelativePaths(declared, 'slice scope path');
|
|
395
|
+
|
|
396
|
+
const explicitBaseBranch = typeof options.baseBranch === 'string' ? options.baseBranch.trim() : '';
|
|
397
|
+
const candidateBaseBranches = Array.from(new Set([
|
|
398
|
+
explicitBaseBranch,
|
|
399
|
+
slice.baseBranch,
|
|
400
|
+
'main',
|
|
401
|
+
'develop',
|
|
402
|
+
'master',
|
|
403
|
+
].filter(Boolean)));
|
|
404
|
+
|
|
405
|
+
let baseRef = '';
|
|
406
|
+
let baseSource = '';
|
|
407
|
+
for (const candidate of candidateBaseBranches) {
|
|
408
|
+
if (hasRemoteBranch(repoRoot, candidate, remote)) {
|
|
409
|
+
baseRef = `${remote}/${candidate}`;
|
|
410
|
+
baseSource = explicitBaseBranch === candidate ? '--base' : candidate === slice.baseBranch ? 'slice.git.base_branch' : 'fallback';
|
|
411
|
+
break;
|
|
412
|
+
}
|
|
413
|
+
if (hasLocalBranch(repoRoot, candidate)) {
|
|
414
|
+
baseRef = candidate;
|
|
415
|
+
baseSource = explicitBaseBranch === candidate ? '--base' : candidate === slice.baseBranch ? 'slice.git.base_branch' : 'fallback';
|
|
416
|
+
break;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
362
419
|
|
|
363
420
|
let touchedRaw = '';
|
|
364
|
-
if (
|
|
365
|
-
touchedRaw = runGit(['diff', '--name-only',
|
|
366
|
-
|
|
367
|
-
touchedRaw = runGit(['diff', '--name-only', 'develop...HEAD'], repoRoot);
|
|
421
|
+
if (baseRef) {
|
|
422
|
+
touchedRaw = runGit(['diff', '--name-only', `${baseRef}...HEAD`], repoRoot);
|
|
423
|
+
console.log(`INFO: check-scope base: ${baseRef} (${baseSource}).`);
|
|
368
424
|
} else {
|
|
369
|
-
console.log(
|
|
425
|
+
console.log(`WARN: No se encontro base para check-scope. Probadas: ${candidateBaseBranches.join(', ')}. Usa --base <branch> o configura git.base_branch en slice.json.`);
|
|
370
426
|
return;
|
|
371
427
|
}
|
|
372
428
|
|
|
373
429
|
if (!touchedRaw) {
|
|
374
|
-
console.log(
|
|
430
|
+
console.log(`WARN: No se encontraron archivos modificados respecto de ${baseRef}.`);
|
|
375
431
|
return;
|
|
376
432
|
}
|
|
377
433
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const { statusPorcelain } = require('./git');
|
|
2
2
|
const { normalizeContextPath } = require('./ai/safety');
|
|
3
3
|
const { checkScope } = require('./readiness');
|
|
4
|
+
const { validateProjectRelativePaths } = require('./paths');
|
|
4
5
|
|
|
5
6
|
class ScopeValidationError extends Error {
|
|
6
7
|
constructor(code, message, details = {}) {
|
|
@@ -19,6 +20,48 @@ function normalizeScopePath(filePath) {
|
|
|
19
20
|
return normalizeContextPath(filePath);
|
|
20
21
|
}
|
|
21
22
|
|
|
23
|
+
function globToRegExp(pattern) {
|
|
24
|
+
const source = String(pattern || '')
|
|
25
|
+
.split('')
|
|
26
|
+
.map((char, index, chars) => {
|
|
27
|
+
if (char === '*') {
|
|
28
|
+
return chars[index + 1] === '*' ? '\0' : '[^/]*';
|
|
29
|
+
}
|
|
30
|
+
if (char === '\0') {
|
|
31
|
+
return '.*';
|
|
32
|
+
}
|
|
33
|
+
return /[\\^$+?.()|[\]{}]/.test(char) ? `\\${char}` : char;
|
|
34
|
+
})
|
|
35
|
+
.join('')
|
|
36
|
+
.replace(/\0\[\^\/\]\*/g, '.*');
|
|
37
|
+
|
|
38
|
+
return new RegExp(`^${source}$`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function allowedPathMatches(filePath, allowedPath) {
|
|
42
|
+
const file = normalizeScopePath(filePath);
|
|
43
|
+
const allowed = normalizeScopePath(allowedPath);
|
|
44
|
+
|
|
45
|
+
if (!file || !allowed) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (file === allowed) {
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (allowed.endsWith('/**')) {
|
|
54
|
+
const prefix = allowed.slice(0, -3);
|
|
55
|
+
return file === prefix || file.startsWith(`${prefix}/`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (allowed.includes('*')) {
|
|
59
|
+
return globToRegExp(allowed).test(file);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
|
|
22
65
|
function parseStatusPorcelain(text) {
|
|
23
66
|
if (!text) {
|
|
24
67
|
return [];
|
|
@@ -74,30 +117,30 @@ function diffWorktreeSnapshots(beforeSnapshot, afterSnapshot) {
|
|
|
74
117
|
}
|
|
75
118
|
|
|
76
119
|
function validateScopeSnapshot({ allowedFiles = [], beforeSnapshot, afterSnapshot, strict = true } = {}) {
|
|
77
|
-
const normalizedAllowedFiles = new Set(
|
|
120
|
+
const normalizedAllowedFiles = Array.from(new Set(
|
|
78
121
|
Array.isArray(allowedFiles)
|
|
79
|
-
? allowedFiles.map(normalizeScopePath).filter(Boolean)
|
|
122
|
+
? validateProjectRelativePaths(allowedFiles, 'allowed scope path').map(normalizeScopePath).filter(Boolean)
|
|
80
123
|
: [],
|
|
81
|
-
);
|
|
124
|
+
));
|
|
82
125
|
const changedFiles = diffWorktreeSnapshots(beforeSnapshot, afterSnapshot);
|
|
83
|
-
const outOfScopeFiles = changedFiles.filter((file) => !normalizedAllowedFiles.
|
|
126
|
+
const outOfScopeFiles = changedFiles.filter((file) => !normalizedAllowedFiles.some((allowedFile) => allowedPathMatches(file, allowedFile)));
|
|
84
127
|
|
|
85
128
|
if (outOfScopeFiles.length === 0) {
|
|
86
129
|
return {
|
|
87
130
|
ok: true,
|
|
88
131
|
changedFiles,
|
|
89
132
|
outOfScopeFiles,
|
|
90
|
-
allowedFiles:
|
|
133
|
+
allowedFiles: normalizedAllowedFiles,
|
|
91
134
|
beforeSnapshot,
|
|
92
135
|
afterSnapshot,
|
|
93
136
|
};
|
|
94
137
|
}
|
|
95
138
|
|
|
96
139
|
const message = formatError(
|
|
97
|
-
`scope violation detected: changed files outside slice
|
|
140
|
+
`scope violation detected: changed files outside declared slice scope: ${outOfScopeFiles.join(', ')}`,
|
|
98
141
|
);
|
|
99
142
|
const error = new ScopeValidationError('SCOPE_VIOLATION', message, {
|
|
100
|
-
allowedFiles:
|
|
143
|
+
allowedFiles: normalizedAllowedFiles,
|
|
101
144
|
beforeSnapshot,
|
|
102
145
|
afterSnapshot,
|
|
103
146
|
changedFiles,
|
|
@@ -112,7 +155,7 @@ function validateScopeSnapshot({ allowedFiles = [], beforeSnapshot, afterSnapsho
|
|
|
112
155
|
ok: false,
|
|
113
156
|
changedFiles,
|
|
114
157
|
outOfScopeFiles,
|
|
115
|
-
allowedFiles:
|
|
158
|
+
allowedFiles: normalizedAllowedFiles,
|
|
116
159
|
beforeSnapshot,
|
|
117
160
|
afterSnapshot,
|
|
118
161
|
error,
|
|
@@ -121,6 +164,7 @@ function validateScopeSnapshot({ allowedFiles = [], beforeSnapshot, afterSnapsho
|
|
|
121
164
|
|
|
122
165
|
module.exports = {
|
|
123
166
|
ScopeValidationError,
|
|
167
|
+
allowedPathMatches,
|
|
124
168
|
captureWorktreeSnapshot,
|
|
125
169
|
diffWorktreeSnapshots,
|
|
126
170
|
checkScope,
|
|
@@ -66,6 +66,15 @@ function sortFileList(files) {
|
|
|
66
66
|
return Array.from(new Set((Array.isArray(files) ? files : []).map((file) => String(file)).filter(Boolean))).sort((a, b) => a.localeCompare(b));
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
function resolveWriteScope(json) {
|
|
70
|
+
const allowedWritePaths = sortFileList(json?.allowed_write_paths);
|
|
71
|
+
if (allowedWritePaths.length > 0) {
|
|
72
|
+
return allowedWritePaths;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return sortFileList(json?.files);
|
|
76
|
+
}
|
|
77
|
+
|
|
69
78
|
function normalizeDependencyRef(slice, dependency) {
|
|
70
79
|
const dep = String(dependency || '').trim();
|
|
71
80
|
if (!dep) {
|
|
@@ -95,6 +104,61 @@ function normalizeDependencyRef(slice, dependency) {
|
|
|
95
104
|
return `${slice.specSlug}/${dep}`;
|
|
96
105
|
}
|
|
97
106
|
|
|
107
|
+
function readSliceFile(rootDir, rootName, specSlug, sliceDirName) {
|
|
108
|
+
const sliceDir = path.join(rootDir, rootName, specSlug, 'slices', sliceDirName);
|
|
109
|
+
const slicePath = path.join(sliceDir, 'slice.json');
|
|
110
|
+
if (!fs.existsSync(slicePath)) {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const json = parseJsonWithComments(fs.readFileSync(slicePath, 'utf8'));
|
|
115
|
+
const sliceId = String(json.slice_id || sliceDirName).trim();
|
|
116
|
+
const ref = `${specSlug}/${sliceId}`;
|
|
117
|
+
return {
|
|
118
|
+
ref,
|
|
119
|
+
specFamily: rootName,
|
|
120
|
+
specSlug,
|
|
121
|
+
sliceId,
|
|
122
|
+
slicePath,
|
|
123
|
+
sliceDir,
|
|
124
|
+
files: resolveWriteScope(json),
|
|
125
|
+
expected_read_paths: sortFileList(json.expected_read_paths),
|
|
126
|
+
allowed_write_paths: sortFileList(json.allowed_write_paths),
|
|
127
|
+
validation_hints: sortFileList(json.validation_hints),
|
|
128
|
+
dependencies: Array.isArray(json.dependencies) ? json.dependencies.map((item) => String(item).trim()).filter(Boolean) : [],
|
|
129
|
+
depends_on: Array.isArray(json.depends_on) ? json.depends_on.map((item) => String(item).trim()).filter(Boolean) : [],
|
|
130
|
+
parallel_safe: typeof json.parallel_safe === 'string' ? json.parallel_safe : null,
|
|
131
|
+
parallel_safe_reason: typeof json.parallel_safe_reason === 'string' ? json.parallel_safe_reason : null,
|
|
132
|
+
status: typeof json.status === 'string' ? json.status : 'draft',
|
|
133
|
+
ticket: typeof json.ticket === 'string' ? json.ticket : '',
|
|
134
|
+
title: typeof json.title === 'string' ? json.title : sliceId,
|
|
135
|
+
json,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function readSpecSlices(rootDir, rootName, specSlug) {
|
|
140
|
+
const slicesDir = path.join(rootDir, rootName, specSlug, 'slices');
|
|
141
|
+
if (!fs.existsSync(slicesDir)) {
|
|
142
|
+
return [];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const slices = [];
|
|
146
|
+
|
|
147
|
+
for (const sliceEntry of fs.readdirSync(slicesDir, { withFileTypes: true })) {
|
|
148
|
+
if (!sliceEntry.isDirectory() || isPlaceholderSliceDir(sliceEntry.name)) {
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const slice = readSliceFile(rootDir, rootName, specSlug, sliceEntry.name);
|
|
153
|
+
if (slice) {
|
|
154
|
+
slices.push(slice);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
slices.sort((left, right) => compareSliceRefs(left.ref, right.ref));
|
|
159
|
+
return slices;
|
|
160
|
+
}
|
|
161
|
+
|
|
98
162
|
function readAllSlices(rootDir) {
|
|
99
163
|
const roots = ['specs', 'specs-fix'];
|
|
100
164
|
const slices = [];
|
|
@@ -110,44 +174,7 @@ function readAllSlices(rootDir) {
|
|
|
110
174
|
continue;
|
|
111
175
|
}
|
|
112
176
|
|
|
113
|
-
|
|
114
|
-
const slicesDir = path.join(rootPath, specSlug, 'slices');
|
|
115
|
-
if (!fs.existsSync(slicesDir)) {
|
|
116
|
-
continue;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
for (const sliceEntry of fs.readdirSync(slicesDir, { withFileTypes: true })) {
|
|
120
|
-
if (!sliceEntry.isDirectory() || isPlaceholderSliceDir(sliceEntry.name)) {
|
|
121
|
-
continue;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const sliceDir = path.join(slicesDir, sliceEntry.name);
|
|
125
|
-
const slicePath = path.join(sliceDir, 'slice.json');
|
|
126
|
-
if (!fs.existsSync(slicePath)) {
|
|
127
|
-
continue;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const json = parseJsonWithComments(fs.readFileSync(slicePath, 'utf8'));
|
|
131
|
-
const sliceId = String(json.slice_id || sliceEntry.name).trim();
|
|
132
|
-
const ref = `${specSlug}/${sliceId}`;
|
|
133
|
-
slices.push({
|
|
134
|
-
ref,
|
|
135
|
-
specFamily: rootName,
|
|
136
|
-
specSlug,
|
|
137
|
-
sliceId,
|
|
138
|
-
slicePath,
|
|
139
|
-
sliceDir,
|
|
140
|
-
files: sortFileList(json.files),
|
|
141
|
-
dependencies: Array.isArray(json.dependencies) ? json.dependencies.map((item) => String(item).trim()).filter(Boolean) : [],
|
|
142
|
-
depends_on: Array.isArray(json.depends_on) ? json.depends_on.map((item) => String(item).trim()).filter(Boolean) : [],
|
|
143
|
-
parallel_safe: typeof json.parallel_safe === 'string' ? json.parallel_safe : null,
|
|
144
|
-
parallel_safe_reason: typeof json.parallel_safe_reason === 'string' ? json.parallel_safe_reason : null,
|
|
145
|
-
status: typeof json.status === 'string' ? json.status : 'draft',
|
|
146
|
-
ticket: typeof json.ticket === 'string' ? json.ticket : '',
|
|
147
|
-
title: typeof json.title === 'string' ? json.title : sliceId,
|
|
148
|
-
json,
|
|
149
|
-
});
|
|
150
|
-
}
|
|
177
|
+
slices.push(...readSpecSlices(rootDir, rootName, specEntry.name));
|
|
151
178
|
}
|
|
152
179
|
}
|
|
153
180
|
|
|
@@ -171,6 +198,75 @@ function declaredDependenciesForSlice(slice) {
|
|
|
171
198
|
return null;
|
|
172
199
|
}
|
|
173
200
|
|
|
201
|
+
function readSliceByRef(rootDir, ref) {
|
|
202
|
+
const value = String(ref || '').trim();
|
|
203
|
+
const slashIndex = value.indexOf('/');
|
|
204
|
+
if (slashIndex === -1) {
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const specSlug = value.slice(0, slashIndex);
|
|
209
|
+
const sliceId = value.slice(slashIndex + 1);
|
|
210
|
+
if (!specSlug || !sliceId) {
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
for (const rootName of ['specs', 'specs-fix']) {
|
|
215
|
+
const direct = readSliceFile(rootDir, rootName, specSlug, sliceId);
|
|
216
|
+
if (direct) {
|
|
217
|
+
return direct;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
for (const slice of readSpecSlices(rootDir, rootName, specSlug)) {
|
|
221
|
+
if (slice.ref === value) {
|
|
222
|
+
return slice;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function readSlicesForSpec(rootDir, specSlug) {
|
|
231
|
+
const targetSpec = String(specSlug || '').trim();
|
|
232
|
+
if (!targetSpec) {
|
|
233
|
+
return readAllSlices(rootDir);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const byRef = new Map();
|
|
237
|
+
const queue = [];
|
|
238
|
+
|
|
239
|
+
function addSlice(slice) {
|
|
240
|
+
if (!slice || byRef.has(slice.ref)) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
byRef.set(slice.ref, slice);
|
|
244
|
+
queue.push(slice);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
for (const rootName of ['specs', 'specs-fix']) {
|
|
248
|
+
for (const slice of readSpecSlices(rootDir, rootName, targetSpec)) {
|
|
249
|
+
addSlice(slice);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
while (queue.length > 0) {
|
|
254
|
+
const slice = queue.shift();
|
|
255
|
+
for (const dep of declaredDependenciesForSlice(slice) || []) {
|
|
256
|
+
if (byRef.has(dep)) {
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const dependencySlice = readSliceByRef(rootDir, dep);
|
|
261
|
+
if (dependencySlice) {
|
|
262
|
+
addSlice(dependencySlice);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return Array.from(byRef.values()).sort((left, right) => compareSliceRefs(left.ref, right.ref));
|
|
268
|
+
}
|
|
269
|
+
|
|
174
270
|
function inferDependencies(slices) {
|
|
175
271
|
const bySpec = new Map();
|
|
176
272
|
const normalized = slices.map((slice) => ({
|
|
@@ -454,7 +550,11 @@ module.exports = {
|
|
|
454
550
|
detectFileConflicts,
|
|
455
551
|
inferDependencies,
|
|
456
552
|
isFoundationSliceId,
|
|
553
|
+
normalizeDeclaredDependencies,
|
|
554
|
+
normalizeDependencyRef,
|
|
457
555
|
readAllSlices,
|
|
556
|
+
readSlicesForSpec,
|
|
458
557
|
naturalNumberFromSliceId,
|
|
558
|
+
resolveWriteScope,
|
|
459
559
|
topoSort,
|
|
460
560
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const { parseJsonWithComments } = require('./json');
|
|
4
|
-
const { normalizeGitBashDrivePath, relativePosixPath, resolveTargetRoot, specRelativePathFromPath, toPosixPath } = require('./paths');
|
|
4
|
+
const { assertPathInsideRoot, normalizeGitBashDrivePath, relativePosixPath, resolveTargetRoot, specRelativePathFromPath, toPosixPath } = require('./paths');
|
|
5
5
|
|
|
6
6
|
function readJson(filePath) {
|
|
7
7
|
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
@@ -42,14 +42,18 @@ function readSliceMeta(slicePath) {
|
|
|
42
42
|
const branchName = typeof git.branch_name === 'string' ? git.branch_name.trim() : '';
|
|
43
43
|
const sliceId = typeof json.slice_id === 'string' ? json.slice_id.trim() : '';
|
|
44
44
|
const status = String(json.status || 'draft').trim() || 'draft';
|
|
45
|
+
const allowedWritePaths = Array.isArray(json.allowed_write_paths) ? json.allowed_write_paths.map((item) => String(item)) : [];
|
|
46
|
+
const legacyFiles = Array.isArray(json.files) ? json.files.map((item) => String(item)) : [];
|
|
45
47
|
|
|
46
48
|
return {
|
|
47
49
|
acceptance: Array.isArray(json.acceptance) ? json.acceptance : [],
|
|
50
|
+
allowedWritePaths,
|
|
48
51
|
baseBranch,
|
|
49
52
|
branchName,
|
|
50
53
|
branchSlug,
|
|
51
54
|
branchType,
|
|
52
|
-
|
|
55
|
+
expectedReadPaths: Array.isArray(json.expected_read_paths) ? json.expected_read_paths.map((item) => String(item)) : [],
|
|
56
|
+
files: allowedWritePaths.length > 0 ? allowedWritePaths : legacyFiles,
|
|
53
57
|
git,
|
|
54
58
|
isBaseline: sliceId.startsWith('slice-00'),
|
|
55
59
|
json,
|
|
@@ -60,6 +64,7 @@ function readSliceMeta(slicePath) {
|
|
|
60
64
|
status,
|
|
61
65
|
tests: Array.isArray(json.tests) ? json.tests : [],
|
|
62
66
|
ticket,
|
|
67
|
+
validationHints: Array.isArray(json.validation_hints) ? json.validation_hints.map((item) => String(item)) : [],
|
|
63
68
|
};
|
|
64
69
|
}
|
|
65
70
|
|
|
@@ -84,9 +89,12 @@ function validateSliceMetaForStart(slice) {
|
|
|
84
89
|
throw new Error(`create-quiver: git.branch_type invalido: "${slice.branchType}". Usa "feature", "bugfix" o "hotfix".`);
|
|
85
90
|
}
|
|
86
91
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
92
|
+
if (!/^[A-Za-z0-9._/-]+$/.test(slice.baseBranch)
|
|
93
|
+
|| slice.baseBranch.includes('..')
|
|
94
|
+
|| slice.baseBranch.startsWith('/')
|
|
95
|
+
|| slice.baseBranch.endsWith('/')
|
|
96
|
+
|| slice.baseBranch.includes('\\')) {
|
|
97
|
+
throw new Error('create-quiver: git.base_branch invalido. Usa una rama base valida como "main", "develop", "master" o "release/2026".');
|
|
90
98
|
}
|
|
91
99
|
|
|
92
100
|
const expectedBranchName = `${slice.branchType}/${slice.ticket}-${slice.branchSlug}`;
|
|
@@ -111,6 +119,7 @@ function resolveRepoSlicePath(repoRoot, relSlicePath) {
|
|
|
111
119
|
function resolveSliceContext(repoRoot, slicePath) {
|
|
112
120
|
const canonicalRepoRoot = canonicalizePath(repoRoot);
|
|
113
121
|
let absSlicePath = resolveSlicePath(slicePath);
|
|
122
|
+
assertPathInsideRoot(canonicalRepoRoot, absSlicePath, 'slice path');
|
|
114
123
|
let relSlicePath = relativePosixPath(canonicalRepoRoot, absSlicePath);
|
|
115
124
|
let parts = relSlicePath.split('/');
|
|
116
125
|
|