create-quiver 0.15.0 → 0.15.2

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 (96) hide show
  1. package/.github/workflows/ci.yml +3 -0
  2. package/CHANGELOG.md +6 -0
  3. package/README.md +33 -0
  4. package/README_FOR_AI.md +13 -6
  5. package/ROADMAP.md +5 -0
  6. package/docs/AI_CONTEXT.md.template +4 -0
  7. package/docs/AI_ONBOARDING_PROMPT.md.template +8 -0
  8. package/docs/CLI_UX_GUIDE.md +31 -2
  9. package/docs/COMMANDS.md.template +16 -5
  10. package/docs/TROUBLESHOOTING.md +70 -0
  11. package/docs/TROUBLESHOOTING.md.template +20 -0
  12. package/docs/WORKFLOW.md.template +2 -2
  13. package/docs/getting-started/installation.md +86 -0
  14. package/docs/reference/commands.md +39 -5
  15. package/package.json +1 -1
  16. package/specs/quiver-v31-ai-model-catalog-agent-selection/EVIDENCE_REPORT.md +185 -0
  17. package/specs/quiver-v31-ai-model-catalog-agent-selection/EXECUTION_PLAN.md +85 -0
  18. package/specs/quiver-v31-ai-model-catalog-agent-selection/SPEC.md +337 -0
  19. package/specs/quiver-v31-ai-model-catalog-agent-selection/STATUS.md +30 -0
  20. package/specs/quiver-v31-ai-model-catalog-agent-selection/pr.md +98 -0
  21. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-00-spec-foundation/CLOSURE_BRIEF.md +30 -0
  22. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-00-spec-foundation/EXECUTION_BRIEF.md +51 -0
  23. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-00-spec-foundation/slice.json +62 -0
  24. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-01-model-catalog-alias-normalization/CLOSURE_BRIEF.md +32 -0
  25. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-01-model-catalog-alias-normalization/EXECUTION_BRIEF.md +53 -0
  26. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-01-model-catalog-alias-normalization/slice.json +72 -0
  27. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-02-interactive-agent-set-selectors/CLOSURE_BRIEF.md +36 -0
  28. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-02-interactive-agent-set-selectors/EXECUTION_BRIEF.md +56 -0
  29. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-02-interactive-agent-set-selectors/slice.json +78 -0
  30. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-03-agent-doctor-repair/CLOSURE_BRIEF.md +36 -0
  31. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-03-agent-doctor-repair/EXECUTION_BRIEF.md +57 -0
  32. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-03-agent-doctor-repair/slice.json +77 -0
  33. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-04-shared-preflight-provider-errors/CLOSURE_BRIEF.md +35 -0
  34. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-04-shared-preflight-provider-errors/EXECUTION_BRIEF.md +55 -0
  35. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-04-shared-preflight-provider-errors/slice.json +84 -0
  36. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-05-ai-models-list/CLOSURE_BRIEF.md +32 -0
  37. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-05-ai-models-list/EXECUTION_BRIEF.md +52 -0
  38. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-05-ai-models-list/slice.json +72 -0
  39. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-06-docs-templates-alignment/CLOSURE_BRIEF.md +33 -0
  40. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-06-docs-templates-alignment/EXECUTION_BRIEF.md +58 -0
  41. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-06-docs-templates-alignment/slice.json +84 -0
  42. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-07-tests-smokes-release-readiness/CLOSURE_BRIEF.md +32 -0
  43. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-07-tests-smokes-release-readiness/EXECUTION_BRIEF.md +59 -0
  44. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-07-tests-smokes-release-readiness/slice.json +94 -0
  45. package/specs/quiver-v32-npx-installation-guidance/EVIDENCE_REPORT.md +26 -0
  46. package/specs/quiver-v32-npx-installation-guidance/SPEC.md +55 -0
  47. package/specs/quiver-v32-npx-installation-guidance/STATUS.md +23 -0
  48. package/specs/quiver-v32-npx-installation-guidance/pr.md +72 -0
  49. package/specs/quiver-v32-npx-installation-guidance/slices/slice-00-installation-docs/CLOSURE_BRIEF.md +31 -0
  50. package/specs/quiver-v32-npx-installation-guidance/slices/slice-00-installation-docs/EXECUTION_BRIEF.md +56 -0
  51. package/specs/quiver-v32-npx-installation-guidance/slices/slice-00-installation-docs/slice.json +75 -0
  52. package/specs/quiver-v33-approval-ux-and-planner-progress/EVIDENCE_REPORT.md +93 -0
  53. package/specs/quiver-v33-approval-ux-and-planner-progress/EXECUTION_PLAN.md +83 -0
  54. package/specs/quiver-v33-approval-ux-and-planner-progress/SPEC.md +158 -0
  55. package/specs/quiver-v33-approval-ux-and-planner-progress/STATUS.md +31 -0
  56. package/specs/quiver-v33-approval-ux-and-planner-progress/pr.md +109 -0
  57. package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-00-approval-ux-foundation/CLOSURE_BRIEF.md +30 -0
  58. package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-00-approval-ux-foundation/EXECUTION_BRIEF.md +56 -0
  59. package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-00-approval-ux-foundation/slice.json +65 -0
  60. package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-01-approval-candidates-model/CLOSURE_BRIEF.md +19 -0
  61. package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-01-approval-candidates-model/EXECUTION_BRIEF.md +51 -0
  62. package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-01-approval-candidates-model/slice.json +74 -0
  63. package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-02-approve-interactive-selection/CLOSURE_BRIEF.md +20 -0
  64. package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-02-approve-interactive-selection/EXECUTION_BRIEF.md +53 -0
  65. package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-02-approve-interactive-selection/slice.json +79 -0
  66. package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-03-technical-plan-review-decision-data/CLOSURE_BRIEF.md +19 -0
  67. package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-03-technical-plan-review-decision-data/EXECUTION_BRIEF.md +50 -0
  68. package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-03-technical-plan-review-decision-data/slice.json +75 -0
  69. package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-04-revise-input-guardrails/CLOSURE_BRIEF.md +19 -0
  70. package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-04-revise-input-guardrails/EXECUTION_BRIEF.md +51 -0
  71. package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-04-revise-input-guardrails/slice.json +73 -0
  72. package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-05-provider-progress-alignment/CLOSURE_BRIEF.md +20 -0
  73. package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-05-provider-progress-alignment/EXECUTION_BRIEF.md +51 -0
  74. package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-05-provider-progress-alignment/slice.json +91 -0
  75. package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-06-workflow-surface-integration/CLOSURE_BRIEF.md +20 -0
  76. package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-06-workflow-surface-integration/EXECUTION_BRIEF.md +53 -0
  77. package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-06-workflow-surface-integration/slice.json +87 -0
  78. package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-07-docs-tests-release-readiness/CLOSURE_BRIEF.md +21 -0
  79. package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-07-docs-tests-release-readiness/EXECUTION_BRIEF.md +55 -0
  80. package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-07-docs-tests-release-readiness/slice.json +97 -0
  81. package/src/create-quiver/commands/ai.js +561 -46
  82. package/src/create-quiver/commands/flow.js +76 -14
  83. package/src/create-quiver/commands/spec.js +8 -1
  84. package/src/create-quiver/index.js +36 -12
  85. package/src/create-quiver/lib/agent-profiles.js +332 -4
  86. package/src/create-quiver/lib/ai/approval-candidates.js +86 -0
  87. package/src/create-quiver/lib/ai/execution-plan.js +7 -2
  88. package/src/create-quiver/lib/ai/executor.js +9 -2
  89. package/src/create-quiver/lib/ai/model-catalog.js +333 -0
  90. package/src/create-quiver/lib/ai/plan-review.js +77 -1
  91. package/src/create-quiver/lib/ai/providers.js +143 -4
  92. package/src/create-quiver/lib/ai/run-state.js +21 -4
  93. package/src/create-quiver/lib/approvals.js +103 -0
  94. package/src/create-quiver/lib/cli/selectors.js +53 -0
  95. package/src/create-quiver/lib/git.js +13 -4
  96. package/src/create-quiver/lib/paths.js +45 -3
@@ -1,6 +1,7 @@
1
1
  const fs = require('node:fs');
2
2
  const path = require('node:path');
3
3
 
4
+ const { redactSecrets, truncateText } = require('./evidence');
4
5
  const { quiverInternalPaths } = require('./init-layout');
5
6
 
6
7
  const PLANNER_APPROVAL_PHASES = Object.freeze(['acceptance', 'technical-plan']);
@@ -185,6 +186,107 @@ function renderApprovalStatus(report) {
185
186
  return `approved ${report.phase}`;
186
187
  }
187
188
 
189
+ function safePreview(text, maxLength = 180) {
190
+ const firstLines = String(text || '')
191
+ .split(/\r?\n/)
192
+ .map((line) => line.trim())
193
+ .filter(Boolean)
194
+ .slice(0, 3)
195
+ .join(' ');
196
+ const truncated = truncateText(redactSecrets(firstLines), maxLength);
197
+ return {
198
+ text: truncated.text.replace(/\s+/g, ' ').trim(),
199
+ truncated: truncated.truncated,
200
+ };
201
+ }
202
+
203
+ function readCandidatePreview(projectRoot, draft) {
204
+ const draftPath = draft?.path || '';
205
+ if (!draftPath) {
206
+ return safePreview('');
207
+ }
208
+ const resolved = path.resolve(projectRoot, draftPath);
209
+ if (!fs.existsSync(resolved)) {
210
+ return {
211
+ text: '(missing draft artifact)',
212
+ truncated: false,
213
+ };
214
+ }
215
+ return safePreview(fs.readFileSync(resolved, 'utf8'));
216
+ }
217
+
218
+ function buildApprovalCandidate(projectRoot, phase, draft, latestVersion, report) {
219
+ const version = Number(draft.version || 0) || null;
220
+ const isLatest = Boolean(version && latestVersion && version === latestVersion);
221
+ const artifactExists = Boolean(draft.path && fs.existsSync(path.resolve(projectRoot, draft.path)));
222
+ const preview = readCandidatePreview(projectRoot, draft);
223
+ const approvable = isLatest && artifactExists && (report.status === 'draft' || report.status === 'stale' || report.status === 'approved');
224
+ const nextCommand = version
225
+ ? `npx create-quiver ai approve --phase ${phase} --version ${version}`
226
+ : `npx create-quiver ai approve --phase ${phase} --version <n>`;
227
+
228
+ return {
229
+ phase,
230
+ version,
231
+ label: version ? `v${version}` : 'unknown version',
232
+ path: draft.path || '',
233
+ source_file: draft.source_file || '',
234
+ created_at: draft.created_at || '',
235
+ raw_artifact_path: draft.raw_artifact_path || null,
236
+ output_source: draft.output_source || null,
237
+ input_compaction: draft.input_compaction || null,
238
+ current: isLatest,
239
+ latest: isLatest,
240
+ recommended: approvable,
241
+ approvable,
242
+ blocked: !approvable,
243
+ status: approvable ? 'approvable' : isLatest ? 'blocked' : 'history',
244
+ reason: approvable
245
+ ? 'latest draft is eligible for approval'
246
+ : isLatest
247
+ ? 'latest draft artifact is missing or not eligible'
248
+ : `not current; latest draft version is ${latestVersion || 'unknown'}`,
249
+ preview: preview.text,
250
+ preview_truncated: preview.truncated,
251
+ next_command: nextCommand,
252
+ recommended_action: approvable ? 'approve' : 'inspect',
253
+ review: null,
254
+ };
255
+ }
256
+
257
+ function buildPlannerApprovalCandidates(projectRoot, phase) {
258
+ const normalizedPhase = normalizePhase(phase);
259
+ if (!PLANNER_APPROVAL_PHASES.includes(normalizedPhase)) {
260
+ throw new Error(formatError(`approval candidates are only supported for planner phases: ${PLANNER_APPROVAL_PHASES.join(', ')}`));
261
+ }
262
+
263
+ const report = readPhaseApproval(projectRoot, normalizedPhase);
264
+ const drafts = normalizeDrafts(report.meta);
265
+ const latestVersion = latestDraftVersion(report.meta);
266
+ const candidates = drafts.map((draft) => buildApprovalCandidate(projectRoot, normalizedPhase, draft, latestVersion, report));
267
+ const current = candidates.find((candidate) => candidate.current) || null;
268
+ const recommended = candidates.find((candidate) => candidate.recommended) || null;
269
+
270
+ return {
271
+ phase: normalizedPhase,
272
+ approval_status: report.status,
273
+ latest_version: latestVersion,
274
+ current,
275
+ recommended,
276
+ candidates,
277
+ history: candidates.filter((candidate) => !candidate.current),
278
+ approved: report.approved
279
+ ? {
280
+ path: report.approved.path,
281
+ version: Number(report.meta?.approved?.version || 0) || null,
282
+ source_file: report.meta?.approved?.source_file || '',
283
+ approved_at: report.meta?.approved?.approved_at || '',
284
+ }
285
+ : null,
286
+ next_command: recommended?.next_command || `npx create-quiver ai plan --phase ${normalizedPhase}${normalizedPhase === 'acceptance' ? ' --input <requirements.md>' : ''} --dry-run`,
287
+ };
288
+ }
289
+
188
290
  function writeApprovalArtifacts(projectRoot, phase, kind, sourceFile, contents, options = {}) {
189
291
  const normalizedPhase = normalizePhase(phase);
190
292
  const root = approvalRoot(projectRoot, normalizedPhase);
@@ -372,6 +474,7 @@ module.exports = {
372
474
  approvePlannerPhase,
373
475
  findDraftVersion,
374
476
  latestDraftVersion,
477
+ buildPlannerApprovalCandidates,
375
478
  normalizePhase,
376
479
  readPhaseApproval,
377
480
  renderApprovalStatus,
@@ -101,7 +101,60 @@ async function selectOption(message, rawOptions, options = {}) {
101
101
  ].join('\n')));
102
102
  }
103
103
 
104
+ async function promptText(message, options = {}) {
105
+ const explicit = String(options.value || '').trim();
106
+ if (explicit) return explicit;
107
+
108
+ const mode = resolveUxMode({
109
+ interactive: options.interactive === true,
110
+ json: options.json,
111
+ noColor: options.noColor,
112
+ stdinIsTTY: options.stdinIsTTY,
113
+ stdoutIsTTY: options.stdoutIsTTY,
114
+ stderrIsTTY: options.stderrIsTTY,
115
+ }, options.env || process.env, {
116
+ input: options.input || process.stdin,
117
+ output: options.output || process.stdout,
118
+ error: options.error || process.stderr,
119
+ });
120
+
121
+ if (mode.usePrompts) {
122
+ if (typeof options.promptText === 'function') {
123
+ const value = await options.promptText(message, options);
124
+ const normalized = String(value || '').trim();
125
+ if (!normalized && options.required !== false) {
126
+ throw new Error(formatError(`${options.name || 'input'} is required.`));
127
+ }
128
+ return normalized;
129
+ }
130
+ const clack = await loadClack(options.prompts);
131
+ const value = await clack.text({
132
+ message,
133
+ placeholder: options.placeholder,
134
+ initialValue: options.initialValue,
135
+ validate: options.required === false
136
+ ? undefined
137
+ : (input) => (String(input || '').trim() ? undefined : 'Required'),
138
+ });
139
+ if (clack.isCancel && clack.isCancel(value)) {
140
+ throw new Error(formatError(`${options.name || 'input'} was canceled.`));
141
+ }
142
+ return String(value || '').trim();
143
+ }
144
+
145
+ if (options.defaultValue !== undefined && options.defaultValue !== null) {
146
+ return String(options.defaultValue).trim();
147
+ }
148
+
149
+ const flag = options.flag || '--value';
150
+ throw new Error(formatError([
151
+ `${options.name || 'input'} requires an explicit value.`,
152
+ `Use ${flag} <value> or rerun with --interactive.`,
153
+ ].join('\n')));
154
+ }
155
+
104
156
  module.exports = {
105
157
  normalizeSelectorOptions,
158
+ promptText,
106
159
  selectOption,
107
160
  };
@@ -19,6 +19,14 @@ function tryGit(args, cwd, options = {}) {
19
19
  }
20
20
  }
21
21
 
22
+ function withWindowsLongPaths(args, options = {}) {
23
+ const platform = options.platform || process.platform;
24
+ if (platform !== 'win32') {
25
+ return args;
26
+ }
27
+ return ['-c', 'core.longpaths=true', ...args];
28
+ }
29
+
22
30
  function hasRef(repoRoot, ref) {
23
31
  try {
24
32
  runGit(['show-ref', '--verify', '--quiet', ref], repoRoot);
@@ -45,11 +53,11 @@ function fetchRemote(repoRoot, remote = 'origin', args = ['--prune']) {
45
53
  }
46
54
 
47
55
  function worktreePrune(repoRoot) {
48
- tryGit(['worktree', 'prune'], repoRoot);
56
+ tryGit(withWindowsLongPaths(['worktree', 'prune']), repoRoot);
49
57
  }
50
58
 
51
59
  function worktreeList(repoRoot) {
52
- const text = tryGit(['worktree', 'list', '--porcelain'], repoRoot);
60
+ const text = tryGit(withWindowsLongPaths(['worktree', 'list', '--porcelain']), repoRoot);
53
61
  const entries = [];
54
62
  const chunks = text.trim().split('\n\n').filter(Boolean);
55
63
 
@@ -79,7 +87,7 @@ function worktreeAdd(repoRoot, worktreePath, ref, options = {}) {
79
87
  args.push('--force');
80
88
  }
81
89
  args.push(worktreePath, ref);
82
- return runGit(args, repoRoot);
90
+ return runGit(withWindowsLongPaths(args, options), repoRoot);
83
91
  }
84
92
 
85
93
  function worktreeRemove(repoRoot, worktreePath, force = false) {
@@ -88,7 +96,7 @@ function worktreeRemove(repoRoot, worktreePath, force = false) {
88
96
  args.push('--force');
89
97
  }
90
98
  args.push(worktreePath);
91
- return runGit(args, repoRoot);
99
+ return runGit(withWindowsLongPaths(args), repoRoot);
92
100
  }
93
101
 
94
102
  function branchDelete(repoRoot, branchName, force = false) {
@@ -207,6 +215,7 @@ module.exports = {
207
215
  runGit,
208
216
  statusPorcelain,
209
217
  tryGit,
218
+ withWindowsLongPaths,
210
219
  worktreeAdd,
211
220
  worktreeList,
212
221
  worktreePrune,
@@ -84,9 +84,42 @@ function realpathOrResolve(filePath, pathLib = path) {
84
84
  }
85
85
  }
86
86
 
87
- function isPathInsideRoot(root, target, pathLib = path) {
88
- const rootPath = realpathOrResolve(root, pathLib);
89
- const targetPath = realpathOrResolve(target, pathLib);
87
+ function realpathCandidates(filePath, pathLib = path) {
88
+ const normalized = normalizeGitBashDrivePath(filePath, pathLib);
89
+ const candidates = [];
90
+
91
+ const add = (candidate) => {
92
+ if (!candidate) {
93
+ return;
94
+ }
95
+ const resolved = pathLib.resolve(normalizeGitBashDrivePath(candidate, pathLib));
96
+ if (!candidates.includes(resolved)) {
97
+ candidates.push(resolved);
98
+ }
99
+ };
100
+
101
+ try {
102
+ add(fs.realpathSync(normalized));
103
+ } catch {
104
+ // Fall back below for paths that do not exist yet.
105
+ }
106
+
107
+ if (typeof fs.realpathSync.native === 'function') {
108
+ try {
109
+ add(fs.realpathSync.native(normalized));
110
+ } catch {
111
+ // Fall back below for paths that do not exist yet.
112
+ }
113
+ }
114
+
115
+ if (candidates.length === 0) {
116
+ add(normalized);
117
+ }
118
+
119
+ return candidates;
120
+ }
121
+
122
+ function isCandidateInsideRoot(rootPath, targetPath, pathLib = path) {
90
123
  const windowsPath = pathLib === path.win32 || process.platform === 'win32';
91
124
  const comparableRoot = windowsPath ? rootPath.toLowerCase() : rootPath;
92
125
  const comparableTarget = windowsPath ? targetPath.toLowerCase() : targetPath;
@@ -99,6 +132,15 @@ function isPathInsideRoot(root, target, pathLib = path) {
99
132
  return Boolean(relative && !relative.startsWith('..') && !pathLib.isAbsolute(relative));
100
133
  }
101
134
 
135
+ function isPathInsideRoot(root, target, pathLib = path) {
136
+ const rootCandidates = realpathCandidates(root, pathLib);
137
+ const targetCandidates = realpathCandidates(target, pathLib);
138
+
139
+ return rootCandidates.some((rootPath) => targetCandidates.some((targetPath) => (
140
+ isCandidateInsideRoot(rootPath, targetPath, pathLib)
141
+ )));
142
+ }
143
+
102
144
  function assertPathInsideRoot(root, target, label = 'path', pathLib = path) {
103
145
  if (!isPathInsideRoot(root, target, pathLib)) {
104
146
  throw new Error(formatError(`${label} must stay inside the project root: ${toPosixPath(target, pathLib)}`));