create-quiver 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/.github/workflows/ci.yml +7 -30
  2. package/AGENTS.md.template +41 -0
  3. package/CHANGELOG.md +5 -0
  4. package/README.md +53 -9
  5. package/README_FOR_AI.md +36 -14
  6. package/ROADMAP.md +78 -0
  7. package/docs/AI_CONTEXT.md.template +19 -25
  8. package/docs/AI_ONBOARDING_PROMPT.md.template +12 -0
  9. package/docs/CONTEXTO.md.template +4 -17
  10. package/docs/DECISIONS.md.template +18 -0
  11. package/docs/DEEP.md.template +34 -0
  12. package/docs/DOCUMENTATION_GUIDE.md.template +9 -7
  13. package/docs/GITFLOW_PR_GUIDE.md.template +7 -0
  14. package/docs/INDEX.md.template +9 -0
  15. package/docs/QUICK.md.template +27 -0
  16. package/docs/STANDARD.md.template +49 -0
  17. package/docs/STATUS.md.template +2 -2
  18. package/docs/SUPPORT_MATRIX.md.template +7 -4
  19. package/docs/TESTING_GUIDE_FOR_AI.md.template +4 -3
  20. package/docs/TROUBLESHOOTING.md.template +14 -0
  21. package/docs/WORKFLOW.md.template +19 -5
  22. package/package.json +3 -1
  23. package/package.template.json +13 -1
  24. package/scripts/cleanup-slice.sh +2 -172
  25. package/scripts/init-docs.sh +246 -44
  26. package/scripts/package-quiver.sh +5 -0
  27. package/scripts/start-slice.sh +3 -425
  28. package/specs/[project-name]/EVIDENCE_REPORT.md.template +3 -1
  29. package/specs/[project-name]/slices/slice-template/slice.json +2 -2
  30. package/specs/quiver-v11-existing-project-migration/EVIDENCE_REPORT.md +38 -0
  31. package/specs/quiver-v11-existing-project-migration/SPEC.md +59 -0
  32. package/specs/quiver-v11-existing-project-migration/STATUS.md +26 -0
  33. package/specs/quiver-v11-existing-project-migration/slices/slice-01-non-destructive-migrate-command/slice.json +73 -0
  34. package/specs/quiver-v11-existing-project-migration/slices/slice-02-version-metadata-doctor-upgrade-checks/slice.json +71 -0
  35. package/specs/quiver-v11-existing-project-migration/slices/slice-03-upgrade-docs-legacy-project-smokes/slice.json +78 -0
  36. package/specs/quiver-v12-cross-platform-native-runtime/EVIDENCE_REPORT.md +30 -0
  37. package/specs/quiver-v12-cross-platform-native-runtime/SPEC.md +86 -0
  38. package/specs/quiver-v12-cross-platform-native-runtime/STATUS.md +29 -0
  39. package/specs/quiver-v12-cross-platform-native-runtime/slices/slice-01-cross-platform-support-contract/slice.json +69 -0
  40. package/specs/quiver-v12-cross-platform-native-runtime/slices/slice-02-node-init-docs-runtime/slice.json +76 -0
  41. package/specs/quiver-v12-cross-platform-native-runtime/slices/slice-03-node-migrate-analyze-doctor-flow/slice.json +74 -0
  42. package/specs/quiver-v12-cross-platform-native-runtime/slices/slice-04-node-slice-lifecycle-commands/slice.json +81 -0
  43. package/specs/quiver-v12-cross-platform-native-runtime/slices/slice-05-generated-project-scripts-and-migration/slice.json +78 -0
  44. package/specs/quiver-v12-cross-platform-native-runtime/slices/slice-06-cross-platform-ci-release-readiness/slice.json +74 -0
  45. package/specs/quiver-v13-token-efficient-ai-context/EVIDENCE_REPORT.md +28 -0
  46. package/specs/quiver-v13-token-efficient-ai-context/SPEC.md +68 -0
  47. package/specs/quiver-v13-token-efficient-ai-context/STATUS.md +26 -0
  48. package/specs/quiver-v13-token-efficient-ai-context/slices/slice-01-token-efficient-ai-modes-guidance/slice.json +65 -0
  49. package/specs/quiver-v13-token-efficient-ai-context/slices/slice-02-decision-log-context-checkpoint/slice.json +64 -0
  50. package/specs/quiver-v13-token-efficient-ai-context/slices/slice-03-project-map-reading-order/slice.json +66 -0
  51. package/specs/quiver-v14-tiered-context-pack/EVIDENCE_REPORT.md +42 -0
  52. package/specs/quiver-v14-tiered-context-pack/SPEC.md +116 -0
  53. package/specs/quiver-v14-tiered-context-pack/STATUS.md +35 -0
  54. package/specs/quiver-v14-tiered-context-pack/slices/slice-01-tiered-context-pack/slice.json +77 -0
  55. package/specs/quiver-v14-tiered-context-pack/slices/slice-02-agents-md-router/slice.json +74 -0
  56. package/specs/quiver-v14-tiered-context-pack/slices/slice-03-active-slice-lifecycle/slice.json +74 -0
  57. package/specs/quiver-v14-tiered-context-pack/slices/slice-04-dedup-frontmatter/slice.json +83 -0
  58. package/specs/quiver-v14-tiered-context-pack/slices/slice-05-doctor-smokes-tiered-pack/slice.json +84 -0
  59. package/src/create-quiver/index.js +360 -40
  60. package/src/create-quiver/lib/analyze.js +9 -0
  61. package/src/create-quiver/lib/doctor.js +212 -0
  62. package/src/create-quiver/lib/git.js +154 -0
  63. package/src/create-quiver/lib/init-docs.js +616 -0
  64. package/src/create-quiver/lib/lifecycle.js +478 -0
  65. package/src/create-quiver/lib/paths.js +19 -0
  66. package/src/create-quiver/lib/readiness.js +300 -0
  67. package/src/create-quiver/lib/scope.js +5 -0
  68. package/src/create-quiver/lib/slice.js +194 -0
  69. package/src/create-quiver/lib/state.js +89 -0
@@ -0,0 +1,212 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { worktreeList } = require('./git');
4
+
5
+ function readTextIfExists(filePath) {
6
+ if (!fs.existsSync(filePath)) {
7
+ return null;
8
+ }
9
+
10
+ return fs.readFileSync(filePath, 'utf8');
11
+ }
12
+
13
+ function countNonEmptyLines(text) {
14
+ return String(text || '')
15
+ .split(/\r?\n/)
16
+ .filter((line) => line.trim().length > 0)
17
+ .length;
18
+ }
19
+
20
+ function hasFrontMatter(text) {
21
+ const value = String(text || '');
22
+ if (!value.startsWith('---\n')) {
23
+ return false;
24
+ }
25
+
26
+ return value.indexOf('\n---\n', 4) !== -1;
27
+ }
28
+
29
+ function normalizeRelativePath(root, absolutePath) {
30
+ return path.relative(root, absolutePath).split(path.sep).join('/');
31
+ }
32
+
33
+ function collectAiMarkdownFiles(projectRoot) {
34
+ const aiDir = path.join(projectRoot, 'docs', 'ai');
35
+ if (!fs.existsSync(aiDir)) {
36
+ return [];
37
+ }
38
+
39
+ const files = [];
40
+
41
+ const walk = (dirPath) => {
42
+ for (const entry of fs.readdirSync(dirPath, { withFileTypes: true })) {
43
+ const fullPath = path.join(dirPath, entry.name);
44
+ if (entry.isDirectory()) {
45
+ walk(fullPath);
46
+ } else if (entry.isFile() && entry.name.endsWith('.md')) {
47
+ files.push(fullPath);
48
+ }
49
+ }
50
+ };
51
+
52
+ walk(aiDir);
53
+
54
+ return files;
55
+ }
56
+
57
+ function countDocsAiFrontMatterIssues(projectRoot) {
58
+ const files = collectAiMarkdownFiles(projectRoot);
59
+ const missing = [];
60
+
61
+ for (const filePath of files) {
62
+ const text = readTextIfExists(filePath);
63
+ if (!text || !hasFrontMatter(text)) {
64
+ missing.push(normalizeRelativePath(projectRoot, filePath));
65
+ }
66
+ }
67
+
68
+ return missing;
69
+ }
70
+
71
+ function countAgentsSections(projectRoot) {
72
+ const agentsPath = path.join(projectRoot, 'AGENTS.md');
73
+ const text = readTextIfExists(agentsPath);
74
+ if (!text) {
75
+ return ['AGENTS.md'];
76
+ }
77
+
78
+ const requiredSections = [
79
+ [/^Purpose$/m, 'Purpose'],
80
+ [/^## Reading Budget$/m, 'Reading Budget'],
81
+ [/^## Reading Order$/m, 'Reading Order'],
82
+ [/^## Output Policy$/m, 'Output Policy'],
83
+ [/^## Slice Execution Rules$/m, 'Slice Execution Rules'],
84
+ [/^## Links$/m, 'Links'],
85
+ ];
86
+
87
+ const missing = requiredSections
88
+ .filter(([pattern]) => !pattern.test(text))
89
+ .map(([, label]) => label);
90
+ return missing;
91
+ }
92
+
93
+ function countTieredPackSizeWarnings(projectRoot) {
94
+ const warnings = [];
95
+
96
+ const quickPath = path.join(projectRoot, 'docs', 'ai', 'QUICK.md');
97
+ const quickText = readTextIfExists(quickPath);
98
+ if (quickText && countNonEmptyLines(quickText) > 50) {
99
+ warnings.push(`docs/ai/QUICK.md exceeds the 50 non-empty line budget (${countNonEmptyLines(quickText)})`);
100
+ }
101
+
102
+ const standardPath = path.join(projectRoot, 'docs', 'ai', 'STANDARD.md');
103
+ const standardText = readTextIfExists(standardPath);
104
+ if (standardText && countNonEmptyLines(standardText) > 300) {
105
+ warnings.push(`docs/ai/STANDARD.md exceeds the 300 non-empty line budget (${countNonEmptyLines(standardText)})`);
106
+ }
107
+
108
+ return warnings;
109
+ }
110
+
111
+ function countActiveSliceOrphans(projectRoot) {
112
+ const activeSlicePath = path.join(projectRoot, 'docs', 'ai', 'ACTIVE_SLICE.md');
113
+ if (!fs.existsSync(activeSlicePath)) {
114
+ return [];
115
+ }
116
+
117
+ const activeWorktrees = worktreeList(projectRoot).filter((entry) => {
118
+ const worktreePath = entry.worktree || '';
119
+ if (!worktreePath || worktreePath === projectRoot) {
120
+ return false;
121
+ }
122
+
123
+ return fs.existsSync(path.join(worktreePath, 'WORKTREE_CONTEXT.md'));
124
+ });
125
+
126
+ if (activeWorktrees.length === 0) {
127
+ return ['docs/ai/ACTIVE_SLICE.md exists without an active slice worktree'];
128
+ }
129
+
130
+ return [];
131
+ }
132
+
133
+ function countStackInfoLeaks(projectRoot) {
134
+ const leakPatterns = [
135
+ /Package manager:/i,
136
+ /Detected package manager:/i,
137
+ /Detected primary stack:/i,
138
+ /Primary install:/i,
139
+ /Primary test:/i,
140
+ /Stack summary:/i,
141
+ ];
142
+
143
+ const generatedFiles = [];
144
+ const docsDir = path.join(projectRoot, 'docs');
145
+
146
+ if (fs.existsSync(docsDir)) {
147
+ const walk = (dirPath) => {
148
+ for (const entry of fs.readdirSync(dirPath, { withFileTypes: true })) {
149
+ const fullPath = path.join(dirPath, entry.name);
150
+ if (entry.isDirectory()) {
151
+ walk(fullPath);
152
+ } else if (entry.isFile() && entry.name.endsWith('.md') && path.relative(docsDir, fullPath) !== 'PROJECT_MAP.md') {
153
+ generatedFiles.push(fullPath);
154
+ }
155
+ }
156
+ };
157
+
158
+ walk(docsDir);
159
+ }
160
+
161
+ const leaks = [];
162
+
163
+ for (const filePath of generatedFiles) {
164
+ const text = readTextIfExists(filePath);
165
+ if (!text) {
166
+ continue;
167
+ }
168
+
169
+ if (leakPatterns.some((pattern) => pattern.test(text))) {
170
+ leaks.push(normalizeRelativePath(projectRoot, filePath));
171
+ }
172
+ }
173
+
174
+ return leaks;
175
+ }
176
+
177
+ function collectDoctorWarnings(projectRoot) {
178
+ const warnings = [];
179
+
180
+ const agentsMissing = countAgentsSections(projectRoot);
181
+ if (agentsMissing.length > 0) {
182
+ if (agentsMissing.includes('AGENTS.md')) {
183
+ warnings.push('missing AGENTS.md');
184
+ } else {
185
+ warnings.push(`AGENTS.md is missing required sections: ${agentsMissing.join(', ')}`);
186
+ }
187
+ }
188
+
189
+ for (const issue of countTieredPackSizeWarnings(projectRoot)) {
190
+ warnings.push(issue);
191
+ }
192
+
193
+ const frontMatterIssues = countDocsAiFrontMatterIssues(projectRoot);
194
+ for (const issue of frontMatterIssues) {
195
+ warnings.push(`${issue} is missing YAML front matter`);
196
+ }
197
+
198
+ for (const issue of countActiveSliceOrphans(projectRoot)) {
199
+ warnings.push(issue);
200
+ }
201
+
202
+ const leakIssues = countStackInfoLeaks(projectRoot);
203
+ if (leakIssues.length > 0) {
204
+ warnings.push(`stack information appears outside docs/PROJECT_MAP.md: ${leakIssues.join(', ')}`);
205
+ }
206
+
207
+ return warnings;
208
+ }
209
+
210
+ module.exports = {
211
+ collectDoctorWarnings,
212
+ };
@@ -0,0 +1,154 @@
1
+ const cp = require('child_process');
2
+
3
+ function runGit(args, cwd, options = {}) {
4
+ return cp.execFileSync('git', args, {
5
+ cwd,
6
+ encoding: 'utf8',
7
+ stdio: ['ignore', 'pipe', 'pipe'],
8
+ ...options,
9
+ }).trim();
10
+ }
11
+
12
+ function tryGit(args, cwd, options = {}) {
13
+ try {
14
+ return runGit(args, cwd, options);
15
+ } catch {
16
+ return '';
17
+ }
18
+ }
19
+
20
+ function hasRef(repoRoot, ref) {
21
+ try {
22
+ runGit(['show-ref', '--verify', '--quiet', ref], repoRoot);
23
+ return true;
24
+ } catch {
25
+ return false;
26
+ }
27
+ }
28
+
29
+ function hasLocalBranch(repoRoot, branchName) {
30
+ return hasRef(repoRoot, `refs/heads/${branchName}`);
31
+ }
32
+
33
+ function hasRemoteBranch(repoRoot, branchName, remote = 'origin') {
34
+ return hasRef(repoRoot, `refs/remotes/${remote}/${branchName}`);
35
+ }
36
+
37
+ function fetchBranch(repoRoot, branchName, remote = 'origin') {
38
+ return runGit(['fetch', remote, `${branchName}:${branchName}`], repoRoot);
39
+ }
40
+
41
+ function fetchRemote(repoRoot, remote = 'origin', args = ['--prune']) {
42
+ return runGit(['fetch', remote, ...args], repoRoot);
43
+ }
44
+
45
+ function worktreePrune(repoRoot) {
46
+ tryGit(['worktree', 'prune'], repoRoot);
47
+ }
48
+
49
+ function worktreeList(repoRoot) {
50
+ const text = tryGit(['worktree', 'list', '--porcelain'], repoRoot);
51
+ const entries = [];
52
+ const chunks = text.trim().split('\n\n').filter(Boolean);
53
+
54
+ for (const chunk of chunks) {
55
+ const entry = {};
56
+ for (const line of chunk.split('\n')) {
57
+ const idx = line.indexOf(' ');
58
+ if (idx === -1) {
59
+ continue;
60
+ }
61
+ entry[line.slice(0, idx)] = line.slice(idx + 1);
62
+ }
63
+ if (entry.worktree) {
64
+ entries.push(entry);
65
+ }
66
+ }
67
+
68
+ return entries;
69
+ }
70
+
71
+ function worktreeAdd(repoRoot, worktreePath, ref, options = {}) {
72
+ const args = ['worktree', 'add'];
73
+ if (options.branch) {
74
+ args.push('-b', options.branch);
75
+ }
76
+ if (options.force) {
77
+ args.push('--force');
78
+ }
79
+ args.push(worktreePath, ref);
80
+ return runGit(args, repoRoot);
81
+ }
82
+
83
+ function worktreeRemove(repoRoot, worktreePath, force = false) {
84
+ const args = ['worktree', 'remove'];
85
+ if (force) {
86
+ args.push('--force');
87
+ }
88
+ args.push(worktreePath);
89
+ return runGit(args, repoRoot);
90
+ }
91
+
92
+ function branchDelete(repoRoot, branchName, force = false) {
93
+ return runGit(['branch', force ? '-D' : '-d', branchName], repoRoot);
94
+ }
95
+
96
+ function currentBranch(repoRoot) {
97
+ return tryGit(['branch', '--show-current'], repoRoot);
98
+ }
99
+
100
+ function statusPorcelain(repoRoot) {
101
+ return tryGit(['status', '--porcelain'], repoRoot);
102
+ }
103
+
104
+ function revListCount(repoRoot, range) {
105
+ const output = tryGit(['rev-list', '--count', range], repoRoot);
106
+ return Number(output || '0');
107
+ }
108
+
109
+ function mergeBaseIsAncestor(repoRoot, maybeAncestor, ref) {
110
+ try {
111
+ runGit(['merge-base', '--is-ancestor', maybeAncestor, ref], repoRoot);
112
+ return true;
113
+ } catch {
114
+ return false;
115
+ }
116
+ }
117
+
118
+ function lsRemoteHeads(repoRoot, branchName, remote = 'origin') {
119
+ try {
120
+ runGit(['ls-remote', '--exit-code', '--heads', remote, branchName], repoRoot);
121
+ return true;
122
+ } catch {
123
+ return false;
124
+ }
125
+ }
126
+
127
+ function catFileExists(repoRoot, specRef) {
128
+ try {
129
+ runGit(['cat-file', '-e', specRef], repoRoot);
130
+ return true;
131
+ } catch {
132
+ return false;
133
+ }
134
+ }
135
+
136
+ module.exports = {
137
+ branchDelete,
138
+ catFileExists,
139
+ currentBranch,
140
+ fetchBranch,
141
+ fetchRemote,
142
+ hasLocalBranch,
143
+ hasRemoteBranch,
144
+ lsRemoteHeads,
145
+ mergeBaseIsAncestor,
146
+ revListCount,
147
+ runGit,
148
+ statusPorcelain,
149
+ tryGit,
150
+ worktreeAdd,
151
+ worktreeList,
152
+ worktreePrune,
153
+ worktreeRemove,
154
+ };