create-quiver 0.6.0 → 0.8.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/.claude/settings.local.json +45 -0
- package/.github/workflows/ci.yml +9 -32
- package/AGENTS.md.template +41 -0
- package/BACKLOG.md +139 -0
- package/CHANGELOG.md +17 -0
- package/README.md +68 -14
- package/README_FOR_AI.md +48 -16
- package/ROADMAP.md +100 -0
- package/docs/AI_CONTEXT.md.template +19 -26
- package/docs/AI_ONBOARDING_PROMPT.md.template +16 -0
- package/docs/COMMANDS.md.template +25 -0
- package/docs/CONTEXTO.md.template +4 -17
- package/docs/DECISIONS.md.template +18 -0
- package/docs/DEEP.md.template +34 -0
- package/docs/DOCUMENTATION_GUIDE.md.template +9 -7
- package/docs/GITFLOW_PR_GUIDE.md.template +7 -0
- package/docs/INDEX.md.template +11 -0
- package/docs/QUICK.md.template +27 -0
- package/docs/STANDARD.md.template +49 -0
- package/docs/STATUS.md.template +2 -2
- package/docs/SUPPORT_MATRIX.md.template +16 -4
- package/docs/TESTING_GUIDE_FOR_AI.md.template +4 -3
- package/docs/TROUBLESHOOTING.md.template +14 -0
- package/docs/WORKFLOW.md.template +21 -4
- package/docs/examples/graph.md.template +62 -0
- package/docs/examples/next.md.template +27 -0
- package/docs/examples/plan.md.template +28 -0
- package/package.json +6 -2
- package/package.template.json +16 -0
- package/scripts/check-slice-readiness.sh +6 -4
- package/scripts/cleanup-slice.sh +2 -172
- package/scripts/init-docs.sh +147 -26
- package/scripts/package-quiver.sh +5 -0
- package/scripts/start-slice.sh +3 -425
- package/specs/[project-name]/EVIDENCE_REPORT.md.template +3 -1
- package/specs/[project-name]/HANDOFF.md.template +37 -0
- package/specs/[project-name]/slices/slice-template/slice.json +7 -2
- package/specs/quiver-v08-agent-onboarding-analysis/slices/slice-01-project-scan-command/slice.json +1 -1
- package/specs/quiver-v08-agent-onboarding-analysis/slices/slice-02-ai-onboarding-prompt/slice.json +1 -1
- package/specs/quiver-v08-agent-onboarding-analysis/slices/slice-03-doctor-readme-adoption-flow/slice.json +1 -1
- package/specs/quiver-v12-cross-platform-native-runtime/EVIDENCE_REPORT.md +30 -0
- package/specs/quiver-v12-cross-platform-native-runtime/SPEC.md +86 -0
- package/specs/quiver-v12-cross-platform-native-runtime/STATUS.md +29 -0
- package/specs/quiver-v12-cross-platform-native-runtime/slices/slice-01-cross-platform-support-contract/slice.json +69 -0
- package/specs/quiver-v12-cross-platform-native-runtime/slices/slice-02-node-init-docs-runtime/slice.json +76 -0
- package/specs/quiver-v12-cross-platform-native-runtime/slices/slice-03-node-migrate-analyze-doctor-flow/slice.json +74 -0
- package/specs/quiver-v12-cross-platform-native-runtime/slices/slice-04-node-slice-lifecycle-commands/slice.json +81 -0
- package/specs/quiver-v12-cross-platform-native-runtime/slices/slice-05-generated-project-scripts-and-migration/slice.json +78 -0
- package/specs/quiver-v12-cross-platform-native-runtime/slices/slice-06-cross-platform-ci-release-readiness/slice.json +74 -0
- package/specs/quiver-v13-token-efficient-ai-context/EVIDENCE_REPORT.md +28 -0
- package/specs/quiver-v13-token-efficient-ai-context/SPEC.md +68 -0
- package/specs/quiver-v13-token-efficient-ai-context/STATUS.md +26 -0
- package/specs/quiver-v13-token-efficient-ai-context/slices/slice-01-token-efficient-ai-modes-guidance/slice.json +65 -0
- package/specs/quiver-v13-token-efficient-ai-context/slices/slice-02-decision-log-context-checkpoint/slice.json +64 -0
- package/specs/quiver-v13-token-efficient-ai-context/slices/slice-03-project-map-reading-order/slice.json +66 -0
- package/specs/quiver-v14-tiered-context-pack/EVIDENCE_REPORT.md +42 -0
- package/specs/quiver-v14-tiered-context-pack/SPEC.md +116 -0
- package/specs/quiver-v14-tiered-context-pack/STATUS.md +35 -0
- package/specs/quiver-v14-tiered-context-pack/slices/slice-01-tiered-context-pack/slice.json +77 -0
- package/specs/quiver-v14-tiered-context-pack/slices/slice-02-agents-md-router/slice.json +74 -0
- package/specs/quiver-v14-tiered-context-pack/slices/slice-03-active-slice-lifecycle/slice.json +74 -0
- package/specs/quiver-v14-tiered-context-pack/slices/slice-04-dedup-frontmatter/slice.json +83 -0
- package/specs/quiver-v14-tiered-context-pack/slices/slice-05-doctor-smokes-tiered-pack/slice.json +84 -0
- package/specs/quiver-v15-init-required-before-migrate/EVIDENCE_REPORT.md +26 -0
- package/specs/quiver-v15-init-required-before-migrate/SPEC.md +66 -0
- package/specs/quiver-v15-init-required-before-migrate/STATUS.md +26 -0
- package/specs/quiver-v15-init-required-before-migrate/slices/slice-01-migrate-initialization-precondition/slice.json +65 -0
- package/specs/quiver-v15-init-required-before-migrate/slices/slice-02-doctor-not-initialized-guidance/slice.json +61 -0
- package/specs/quiver-v15-init-required-before-migrate/slices/slice-03-docs-smokes-init-before-migrate/slice.json +64 -0
- package/specs/quiver-v16-handoff-contract/EVIDENCE_REPORT.md +26 -0
- package/specs/quiver-v16-handoff-contract/SPEC.md +68 -0
- package/specs/quiver-v16-handoff-contract/STATUS.md +26 -0
- package/specs/quiver-v16-handoff-contract/slices/slice-01-handoff-template-and-contract/slice.json +66 -0
- package/specs/quiver-v16-handoff-contract/slices/slice-02-check-handoff-command/slice.json +70 -0
- package/specs/quiver-v16-handoff-contract/slices/slice-03-handoff-scaffold-optional/slice.json +67 -0
- package/specs/quiver-v17-orchestration-foundation/EVIDENCE_REPORT.md +32 -0
- package/specs/quiver-v17-orchestration-foundation/SPEC.md +79 -0
- package/specs/quiver-v17-orchestration-foundation/STATUS.md +31 -0
- package/specs/quiver-v17-orchestration-foundation/slices/slice-01-ci-matrix-verified/slice.json +68 -0
- package/specs/quiver-v17-orchestration-foundation/slices/slice-02-slice-graph-library/slice.json +65 -0
- package/specs/quiver-v17-orchestration-foundation/slices/slice-03-depends-on-validation/slice.json +72 -0
- package/specs/quiver-v18-slice-orchestration/EVIDENCE_REPORT.md +38 -0
- package/specs/quiver-v18-slice-orchestration/SPEC.md +91 -0
- package/specs/quiver-v18-slice-orchestration/STATUS.md +33 -0
- package/specs/quiver-v18-slice-orchestration/slices/slice-01-plan-command/slice.json +79 -0
- package/specs/quiver-v18-slice-orchestration/slices/slice-02-graph-mvp-tree/slice.json +75 -0
- package/specs/quiver-v18-slice-orchestration/slices/slice-03-graph-extended-formats/slice.json +70 -0
- package/specs/quiver-v18-slice-orchestration/slices/slice-04-next-command/slice.json +73 -0
- package/specs/quiver-v18-stabilization/EVIDENCE_REPORT.md +26 -0
- package/specs/quiver-v18-stabilization/SPEC.md +62 -0
- package/specs/quiver-v18-stabilization/STATUS.md +30 -0
- package/specs/quiver-v18-stabilization/slices/slice-01-fix-legacy-dependency-resolution/CLOSURE_BRIEF.md +29 -0
- package/specs/quiver-v18-stabilization/slices/slice-01-fix-legacy-dependency-resolution/EXECUTION_BRIEF.md +134 -0
- package/specs/quiver-v18-stabilization/slices/slice-01-fix-legacy-dependency-resolution/slice.json +56 -0
- package/specs/quiver-v18-stabilization/slices/slice-02-roadmap-and-branch-cleanup/CLOSURE_BRIEF.md +29 -0
- package/specs/quiver-v18-stabilization/slices/slice-02-roadmap-and-branch-cleanup/EXECUTION_BRIEF.md +118 -0
- package/specs/quiver-v18-stabilization/slices/slice-02-roadmap-and-branch-cleanup/slice.json +57 -0
- package/specs/quiver-v18-stabilization/slices/slice-03-publish-drafts-branch/CLOSURE_BRIEF.md +23 -0
- package/specs/quiver-v18-stabilization/slices/slice-03-publish-drafts-branch/EXECUTION_BRIEF.md +73 -0
- package/specs/quiver-v18-stabilization/slices/slice-03-publish-drafts-branch/slice.json +49 -0
- package/src/create-quiver/commands/graph.js +97 -0
- package/src/create-quiver/commands/next.js +134 -0
- package/src/create-quiver/commands/plan.js +205 -0
- package/src/create-quiver/index.js +476 -123
- package/src/create-quiver/lib/analyze.js +9 -0
- package/src/create-quiver/lib/doctor.js +212 -0
- package/src/create-quiver/lib/git.js +154 -0
- package/src/create-quiver/lib/handoff.js +104 -0
- package/src/create-quiver/lib/init-docs.js +674 -0
- package/src/create-quiver/lib/json.js +14 -0
- package/src/create-quiver/lib/lifecycle.js +479 -0
- package/src/create-quiver/lib/paths.js +19 -0
- package/src/create-quiver/lib/readiness.js +354 -0
- package/src/create-quiver/lib/renderers/dot.js +129 -0
- package/src/create-quiver/lib/renderers/mermaid.js +119 -0
- package/src/create-quiver/lib/renderers/tree.js +116 -0
- package/src/create-quiver/lib/scope.js +5 -0
- package/src/create-quiver/lib/slice-graph.js +453 -0
- package/src/create-quiver/lib/slice.js +195 -0
- package/src/create-quiver/lib/state.js +139 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { parseJsonWithComments } = require('./json');
|
|
4
|
+
const { resolveTargetRoot, toPosixPath } = require('./paths');
|
|
5
|
+
|
|
6
|
+
function readJson(filePath) {
|
|
7
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function canonicalizePath(dirPath) {
|
|
11
|
+
try {
|
|
12
|
+
return fs.realpathSync(dirPath);
|
|
13
|
+
} catch {
|
|
14
|
+
return path.resolve(dirPath);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function toAlias(ticket) {
|
|
19
|
+
const parts = String(ticket || '').split('-').filter(Boolean);
|
|
20
|
+
const prefix = (parts[0] || 'GEN').replace(/[^A-Za-z0-9]/g, '').toUpperCase();
|
|
21
|
+
const suffix = (parts[parts.length - 1] || '00').replace(/[^A-Za-z0-9]/g, '').toUpperCase();
|
|
22
|
+
const short = prefix.length <= 3 ? prefix : prefix.slice(0, 3);
|
|
23
|
+
return `${short || 'GEN'}-${suffix || '00'}`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function resolveSlicePath(sliceInput) {
|
|
27
|
+
if (!fs.existsSync(sliceInput)) {
|
|
28
|
+
throw new Error(`create-quiver: no existe el slice '${sliceInput}'.`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return canonicalizePath(sliceInput);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function readSliceMeta(slicePath) {
|
|
35
|
+
const json = parseJsonWithComments(fs.readFileSync(slicePath, 'utf8'));
|
|
36
|
+
const ticket = typeof json.ticket === 'string' ? json.ticket.trim() : '';
|
|
37
|
+
const git = json.git ?? {};
|
|
38
|
+
const branchType = typeof git.branch_type === 'string' ? git.branch_type.trim() : '';
|
|
39
|
+
const baseBranch = typeof git.base_branch === 'string' ? git.base_branch.trim() : '';
|
|
40
|
+
const branchSlug = typeof git.branch_slug === 'string' ? git.branch_slug.trim() : '';
|
|
41
|
+
const branchName = typeof git.branch_name === 'string' ? git.branch_name.trim() : '';
|
|
42
|
+
const sliceId = typeof json.slice_id === 'string' ? json.slice_id.trim() : '';
|
|
43
|
+
const status = String(json.status || 'draft').trim() || 'draft';
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
acceptance: Array.isArray(json.acceptance) ? json.acceptance : [],
|
|
47
|
+
baseBranch,
|
|
48
|
+
branchName,
|
|
49
|
+
branchSlug,
|
|
50
|
+
branchType,
|
|
51
|
+
files: Array.isArray(json.files) ? json.files : [],
|
|
52
|
+
git,
|
|
53
|
+
isBaseline: sliceId.startsWith('slice-00'),
|
|
54
|
+
json,
|
|
55
|
+
sliceId,
|
|
56
|
+
slicePath,
|
|
57
|
+
specFamily: null,
|
|
58
|
+
specSlug: null,
|
|
59
|
+
status,
|
|
60
|
+
tests: Array.isArray(json.tests) ? json.tests : [],
|
|
61
|
+
ticket,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function validateSliceMetaForStart(slice) {
|
|
66
|
+
if (!slice.sliceId) {
|
|
67
|
+
throw new Error('create-quiver: falta "slice_id" en el slice.');
|
|
68
|
+
}
|
|
69
|
+
if (!slice.ticket) {
|
|
70
|
+
throw new Error('create-quiver: falta "ticket" en el slice.');
|
|
71
|
+
}
|
|
72
|
+
if (!slice.branchType || !slice.baseBranch || !slice.branchSlug || !slice.branchName) {
|
|
73
|
+
throw new Error('create-quiver: el bloque "git" debe incluir "branch_type", "base_branch", "branch_slug" y "branch_name".');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const expectedBaseByType = {
|
|
77
|
+
feature: 'develop',
|
|
78
|
+
bugfix: 'develop',
|
|
79
|
+
hotfix: 'main',
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
if (!expectedBaseByType[slice.branchType]) {
|
|
83
|
+
throw new Error(`create-quiver: git.branch_type invalido: "${slice.branchType}". Usa "feature", "bugfix" o "hotfix".`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const expectedBaseBranch = expectedBaseByType[slice.branchType];
|
|
87
|
+
if (slice.baseBranch !== expectedBaseBranch) {
|
|
88
|
+
throw new Error(`create-quiver: git.base_branch invalido para ${slice.branchType}. Esperado: "${expectedBaseBranch}".`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const expectedBranchName = `${slice.branchType}/${slice.ticket}-${slice.branchSlug}`;
|
|
92
|
+
if (slice.branchName !== expectedBranchName) {
|
|
93
|
+
throw new Error(`create-quiver: git.branch_name invalido. Esperado: "${expectedBranchName}".`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function resolveSliceContext(repoRoot, slicePath) {
|
|
98
|
+
const absSlicePath = resolveSlicePath(slicePath);
|
|
99
|
+
const relSlicePath = toPosixPath(path.relative(repoRoot, absSlicePath));
|
|
100
|
+
const parts = relSlicePath.split('/');
|
|
101
|
+
|
|
102
|
+
if (parts[0] !== 'specs' && parts[0] !== 'specs-fix') {
|
|
103
|
+
throw new Error('create-quiver: el slice debe vivir dentro de specs/ o specs-fix/.');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const specFamily = parts[0];
|
|
107
|
+
const specSlug = parts[1];
|
|
108
|
+
const specDirRel = `${specFamily}/${specSlug}`;
|
|
109
|
+
const specDirAbs = path.join(repoRoot, specDirRel);
|
|
110
|
+
const slice = readSliceMeta(absSlicePath);
|
|
111
|
+
slice.specFamily = specFamily;
|
|
112
|
+
slice.specSlug = specSlug;
|
|
113
|
+
slice.specDirRel = specDirRel;
|
|
114
|
+
slice.specDirAbs = specDirAbs;
|
|
115
|
+
slice.sliceRel = relSlicePath;
|
|
116
|
+
slice.sliceAbs = absSlicePath;
|
|
117
|
+
slice.prPath = path.join(path.dirname(absSlicePath), 'pr.md');
|
|
118
|
+
return slice;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function activeSlicePath(repoRoot) {
|
|
122
|
+
return path.join(repoRoot, 'docs', 'ai', 'ACTIVE_SLICE.md');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function renderActiveSlice(slice) {
|
|
126
|
+
const lines = [
|
|
127
|
+
'# Active Slice',
|
|
128
|
+
'',
|
|
129
|
+
'## Slice ID',
|
|
130
|
+
'',
|
|
131
|
+
slice.sliceId || 'slice-unknown',
|
|
132
|
+
'',
|
|
133
|
+
'## Title',
|
|
134
|
+
'',
|
|
135
|
+
slice.json.title || slice.sliceId || 'Untitled slice',
|
|
136
|
+
'',
|
|
137
|
+
'## Objective',
|
|
138
|
+
'',
|
|
139
|
+
slice.json.objective || 'Sin objetivo declarado.',
|
|
140
|
+
'',
|
|
141
|
+
'## allowed_files',
|
|
142
|
+
];
|
|
143
|
+
|
|
144
|
+
if (Array.isArray(slice.files) && slice.files.length > 0) {
|
|
145
|
+
for (const file of slice.files) {
|
|
146
|
+
lines.push(`- ${file}`);
|
|
147
|
+
}
|
|
148
|
+
} else {
|
|
149
|
+
lines.push('- n/a');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
lines.push('', '## Validation Commands');
|
|
153
|
+
if (Array.isArray(slice.tests) && slice.tests.length > 0) {
|
|
154
|
+
for (const command of slice.tests) {
|
|
155
|
+
lines.push(`- ${command}`);
|
|
156
|
+
}
|
|
157
|
+
} else {
|
|
158
|
+
lines.push('- n/a');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
lines.push('', '## Definition of Done');
|
|
162
|
+
if (Array.isArray(slice.acceptance) && slice.acceptance.length > 0) {
|
|
163
|
+
for (const item of slice.acceptance) {
|
|
164
|
+
lines.push(`- ${item}`);
|
|
165
|
+
}
|
|
166
|
+
} else {
|
|
167
|
+
lines.push('- n/a');
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
lines.push('', '## Prohibition', '', 'Do not edit any file outside allowed_files.', '');
|
|
171
|
+
|
|
172
|
+
return `${lines.join('\n')}\n`;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function worktreesRootForRepo(repoRoot, branchName) {
|
|
176
|
+
const repoName = path.basename(repoRoot);
|
|
177
|
+
const repoParent = path.dirname(repoRoot);
|
|
178
|
+
return process.env.SLICE_WORKTREES_DIR || path.join(repoParent, '.worktrees', repoName);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function safeBranchName(branchName) {
|
|
182
|
+
return String(branchName || '').replace(/[^A-Za-z0-9._-]/g, '-');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
module.exports = {
|
|
186
|
+
canonicalizePath,
|
|
187
|
+
readSliceMeta,
|
|
188
|
+
resolveSliceContext,
|
|
189
|
+
safeBranchName,
|
|
190
|
+
activeSlicePath,
|
|
191
|
+
renderActiveSlice,
|
|
192
|
+
toAlias,
|
|
193
|
+
validateSliceMetaForStart,
|
|
194
|
+
worktreesRootForRepo,
|
|
195
|
+
};
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
function statePath(projectRoot) {
|
|
5
|
+
return path.join(projectRoot, '.quiver', 'state.json');
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function ensureDir(dirPath) {
|
|
9
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function readState(projectRoot) {
|
|
13
|
+
const filePath = statePath(projectRoot);
|
|
14
|
+
|
|
15
|
+
if (!fs.existsSync(filePath)) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function hasInitializedStateMetadata(state) {
|
|
23
|
+
return Boolean(
|
|
24
|
+
state
|
|
25
|
+
&& typeof state.initialized_version === 'string'
|
|
26
|
+
&& state.initialized_version.length > 0
|
|
27
|
+
&& typeof state.last_initialized_at === 'string'
|
|
28
|
+
&& state.last_initialized_at.length > 0,
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function hasGeneratedProjectSpec(projectRoot) {
|
|
33
|
+
const specsDir = path.join(projectRoot, 'specs');
|
|
34
|
+
if (!fs.existsSync(specsDir)) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return fs.readdirSync(specsDir, { withFileTypes: true })
|
|
39
|
+
.filter((entry) => entry.isDirectory())
|
|
40
|
+
.map((entry) => entry.name)
|
|
41
|
+
.filter((entry) => entry !== '[project-name]' && !entry.startsWith('quiver-'))
|
|
42
|
+
.some((entry) => (
|
|
43
|
+
fs.existsSync(path.join(specsDir, entry, 'SPEC.md'))
|
|
44
|
+
&& fs.existsSync(path.join(specsDir, entry, 'STATUS.md'))
|
|
45
|
+
&& fs.existsSync(path.join(specsDir, entry, 'EVIDENCE_REPORT.md'))
|
|
46
|
+
&& fs.existsSync(path.join(specsDir, entry, 'slices', 'slice-template', 'slice.json'))
|
|
47
|
+
));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function hasLegacyQuiverInitializationEvidence(projectRoot) {
|
|
51
|
+
const requiredPaths = [
|
|
52
|
+
'docs-template/scripts/init-docs.sh',
|
|
53
|
+
'tools/scripts/start-slice.sh',
|
|
54
|
+
'tools/scripts/check-slice-readiness.sh',
|
|
55
|
+
'.github/pull_request_template.md',
|
|
56
|
+
'docs/INDEX.md',
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
return requiredPaths.every((relativePath) => fs.existsSync(path.join(projectRoot, relativePath)))
|
|
60
|
+
&& hasGeneratedProjectSpec(projectRoot);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function hasQuiverInitializationEvidence(projectRoot) {
|
|
64
|
+
const state = readState(projectRoot);
|
|
65
|
+
return hasInitializedStateMetadata(state) || hasLegacyQuiverInitializationEvidence(projectRoot);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function writeState(projectRoot, nextState) {
|
|
69
|
+
const stateDir = path.join(projectRoot, '.quiver');
|
|
70
|
+
ensureDir(stateDir);
|
|
71
|
+
fs.writeFileSync(statePath(projectRoot), `${JSON.stringify(nextState, null, 2)}\n`);
|
|
72
|
+
return statePath(projectRoot);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function updateStateForInit(projectRoot, projectName, cliVersion) {
|
|
76
|
+
const currentState = readState(projectRoot) || {};
|
|
77
|
+
const now = new Date().toISOString();
|
|
78
|
+
const nextState = {
|
|
79
|
+
...currentState,
|
|
80
|
+
quiver_version: cliVersion,
|
|
81
|
+
project_name: projectName || currentState.project_name || '',
|
|
82
|
+
initialized_version: currentState.initialized_version || cliVersion,
|
|
83
|
+
migrated_version: currentState.migrated_version ?? null,
|
|
84
|
+
last_initialized_at: currentState.last_initialized_at || now,
|
|
85
|
+
last_migration_at: currentState.last_migration_at ?? null,
|
|
86
|
+
last_analysis_at: currentState.last_analysis_at ?? null,
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
writeState(projectRoot, nextState);
|
|
90
|
+
return nextState;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function updateStateForMigrate(projectRoot, projectName, cliVersion) {
|
|
94
|
+
const currentState = readState(projectRoot) || {};
|
|
95
|
+
const now = new Date().toISOString();
|
|
96
|
+
const nextState = {
|
|
97
|
+
...currentState,
|
|
98
|
+
quiver_version: cliVersion,
|
|
99
|
+
project_name: projectName || currentState.project_name || '',
|
|
100
|
+
initialized_version: currentState.initialized_version ?? null,
|
|
101
|
+
migrated_version: cliVersion,
|
|
102
|
+
last_initialized_at: currentState.last_initialized_at ?? null,
|
|
103
|
+
last_migration_at: now,
|
|
104
|
+
last_analysis_at: currentState.last_analysis_at ?? null,
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
writeState(projectRoot, nextState);
|
|
108
|
+
return nextState;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function updateStateForAnalyze(projectRoot, cliVersion) {
|
|
112
|
+
const currentState = readState(projectRoot);
|
|
113
|
+
|
|
114
|
+
if (!currentState) {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const nextState = {
|
|
119
|
+
...currentState,
|
|
120
|
+
quiver_version: cliVersion,
|
|
121
|
+
last_analysis_at: new Date().toISOString(),
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
writeState(projectRoot, nextState);
|
|
125
|
+
return nextState;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
module.exports = {
|
|
129
|
+
hasGeneratedProjectSpec,
|
|
130
|
+
hasInitializedStateMetadata,
|
|
131
|
+
hasLegacyQuiverInitializationEvidence,
|
|
132
|
+
hasQuiverInitializationEvidence,
|
|
133
|
+
readState,
|
|
134
|
+
statePath,
|
|
135
|
+
updateStateForAnalyze,
|
|
136
|
+
updateStateForInit,
|
|
137
|
+
updateStateForMigrate,
|
|
138
|
+
writeState,
|
|
139
|
+
};
|