create-quiver 0.14.0 → 0.14.1

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 (55) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +183 -518
  3. package/README_FOR_AI.md +32 -26
  4. package/ROADMAP.md +5 -0
  5. package/assets/quiver-wordmark.svg +22 -0
  6. package/docs/AI_CONTEXT.md.template +2 -0
  7. package/docs/AI_ONBOARDING_PROMPT.md.template +9 -1
  8. package/docs/CLI_UX_GUIDE.md +125 -0
  9. package/docs/COMMANDS.md.template +16 -3
  10. package/docs/GITFLOW_PR_GUIDE.md +70 -0
  11. package/docs/getting-started/linux.md +84 -0
  12. package/docs/getting-started/macos.md +85 -0
  13. package/docs/getting-started/windows-git-bash-wsl.md +78 -0
  14. package/docs/getting-started/windows-powershell.md +96 -0
  15. package/docs/reference/commands.md +94 -0
  16. package/docs/workflows/existing-project.md +131 -0
  17. package/docs/workflows/full-ai-spec-to-pr.md +311 -0
  18. package/docs/workflows/legacy-quiver-project.md +102 -0
  19. package/docs/workflows/new-project.md +76 -0
  20. package/package.json +5 -1
  21. package/specs/quiver-v29-planner-prepare-context-cli-ux/EVIDENCE_REPORT.md +163 -0
  22. package/specs/quiver-v29-planner-prepare-context-cli-ux/EXECUTION_PLAN.md +72 -0
  23. package/specs/quiver-v29-planner-prepare-context-cli-ux/SPEC.md +173 -0
  24. package/specs/quiver-v29-planner-prepare-context-cli-ux/STATUS.md +34 -0
  25. package/specs/quiver-v29-planner-prepare-context-cli-ux/pr.md +95 -0
  26. package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-00-cli-ux-spec-foundation/CLOSURE_BRIEF.md +32 -0
  27. package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-00-cli-ux-spec-foundation/EXECUTION_BRIEF.md +45 -0
  28. package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-00-cli-ux-spec-foundation/slice.json +58 -0
  29. package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-01-cli-ux-primitives-theme/CLOSURE_BRIEF.md +33 -0
  30. package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-01-cli-ux-primitives-theme/EXECUTION_BRIEF.md +49 -0
  31. package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-01-cli-ux-primitives-theme/slice.json +75 -0
  32. package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-02-planner-context-proposal-contract/CLOSURE_BRIEF.md +32 -0
  33. package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-02-planner-context-proposal-contract/EXECUTION_BRIEF.md +47 -0
  34. package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-02-planner-context-proposal-contract/slice.json +71 -0
  35. package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-03-prepare-context-planner-review-flow/CLOSURE_BRIEF.md +33 -0
  36. package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-03-prepare-context-planner-review-flow/EXECUTION_BRIEF.md +52 -0
  37. package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-03-prepare-context-planner-review-flow/slice.json +82 -0
  38. package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-04-ux-flag-matrix-compatibility/CLOSURE_BRIEF.md +34 -0
  39. package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-04-ux-flag-matrix-compatibility/EXECUTION_BRIEF.md +46 -0
  40. package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-04-ux-flag-matrix-compatibility/slice.json +73 -0
  41. package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-05-progressive-command-adoption/CLOSURE_BRIEF.md +34 -0
  42. package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-05-progressive-command-adoption/EXECUTION_BRIEF.md +46 -0
  43. package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-05-progressive-command-adoption/slice.json +83 -0
  44. package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-06-docs-tests-smoke-readiness/CLOSURE_BRIEF.md +31 -0
  45. package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-06-docs-tests-smoke-readiness/EXECUTION_BRIEF.md +50 -0
  46. package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-06-docs-tests-smoke-readiness/slice.json +95 -0
  47. package/src/create-quiver/commands/ai.js +458 -3
  48. package/src/create-quiver/commands/spec.js +64 -2
  49. package/src/create-quiver/index.js +49 -1
  50. package/src/create-quiver/lib/ai/context-proposal.js +389 -0
  51. package/src/create-quiver/lib/ai/context-proposal.schema.js +31 -0
  52. package/src/create-quiver/lib/cli/editor.js +118 -0
  53. package/src/create-quiver/lib/cli/theme.js +100 -0
  54. package/src/create-quiver/lib/cli/ux-flags.js +151 -0
  55. package/src/create-quiver/lib/cli/ux.js +130 -0
@@ -0,0 +1,100 @@
1
+ const QUIVER_COLORS = Object.freeze({
2
+ sky: '#86C8F2',
3
+ blue: '#6BADEB',
4
+ periwinkle: '#7F9EE8',
5
+ violet: '#9B82E6',
6
+ magenta: '#D56AB0',
7
+ });
8
+
9
+ const STATUS_COLORS = Object.freeze({
10
+ info: 'sky',
11
+ command: 'blue',
12
+ planner: 'violet',
13
+ review: 'magenta',
14
+ risk: 'magenta',
15
+ muted: 'periwinkle',
16
+ });
17
+
18
+ function isTruthy(value) {
19
+ return /^(1|true|yes|on)$/i.test(String(value || '').trim());
20
+ }
21
+
22
+ function isAsciiLocale(env = process.env) {
23
+ const locale = `${env.LC_ALL || ''} ${env.LC_CTYPE || ''} ${env.LANG || ''}`;
24
+ return /\bC\b|ASCII/i.test(locale);
25
+ }
26
+
27
+ function isDumbTerminal(env = process.env) {
28
+ return String(env.TERM || '').toLowerCase() === 'dumb';
29
+ }
30
+
31
+ function shouldUseColor(options = {}, env = process.env, tty = {}) {
32
+ if (options.json || options.noColor) return false;
33
+ if (env.NO_COLOR || env.FORCE_COLOR === '0') return false;
34
+ if (isDumbTerminal(env)) return false;
35
+ if (isTruthy(env.CI)) return false;
36
+ if (tty.stdout === false) return false;
37
+ if (options.color === true) return true;
38
+ return Boolean(tty.stdout);
39
+ }
40
+
41
+ function shouldUseUnicode(options = {}, env = process.env, tty = {}) {
42
+ if (options.ascii || options.json) return false;
43
+ if (isDumbTerminal(env) || isAsciiLocale(env)) return false;
44
+ if (tty.stdout === false) return false;
45
+ if (options.unicode === true) return true;
46
+ return Boolean(tty.stdout);
47
+ }
48
+
49
+ function hexToRgb(hex) {
50
+ const normalized = String(hex || '').replace(/^#/, '');
51
+ if (!/^[0-9a-f]{6}$/i.test(normalized)) {
52
+ return null;
53
+ }
54
+ return [
55
+ Number.parseInt(normalized.slice(0, 2), 16),
56
+ Number.parseInt(normalized.slice(2, 4), 16),
57
+ Number.parseInt(normalized.slice(4, 6), 16),
58
+ ];
59
+ }
60
+
61
+ function colorize(value, colorName, theme) {
62
+ const text = String(value ?? '');
63
+ if (!theme?.useColor) return text;
64
+ const rgb = hexToRgb(theme.colors[colorName] || colorName);
65
+ if (!rgb) return text;
66
+ return `\u001b[38;2;${rgb[0]};${rgb[1]};${rgb[2]}m${text}\u001b[0m`;
67
+ }
68
+
69
+ function resolveTheme(options = {}, env = process.env, tty = {}) {
70
+ const useColor = shouldUseColor(options, env, tty);
71
+ const useUnicode = shouldUseUnicode(options, env, tty);
72
+ const symbols = useUnicode
73
+ ? { start: '◇', success: '✓', warning: '!', error: '✖', bullet: '•' }
74
+ : { start: '*', success: 'OK', warning: 'WARN', error: 'ERR', bullet: '-' };
75
+ const theme = {
76
+ colors: QUIVER_COLORS,
77
+ statusColors: STATUS_COLORS,
78
+ symbols,
79
+ useColor,
80
+ useUnicode,
81
+ };
82
+
83
+ return {
84
+ ...theme,
85
+ colorize: (value, colorName) => colorize(value, colorName, theme),
86
+ status: (status, value) => colorize(value, STATUS_COLORS[status] || 'sky', theme),
87
+ };
88
+ }
89
+
90
+ module.exports = {
91
+ QUIVER_COLORS,
92
+ STATUS_COLORS,
93
+ colorize,
94
+ isAsciiLocale,
95
+ isDumbTerminal,
96
+ isTruthy,
97
+ resolveTheme,
98
+ shouldUseColor,
99
+ shouldUseUnicode,
100
+ };
@@ -0,0 +1,151 @@
1
+ const UX_FLAG_OPTIONS = Object.freeze({
2
+ withPlanner: '--with-planner',
3
+ interactive: '--interactive',
4
+ review: '--review',
5
+ });
6
+
7
+ const UX_FLAG_MATRIX = Object.freeze({
8
+ 'ai prepare-context': Object.freeze({
9
+ withPlanner: true,
10
+ interactive: true,
11
+ review: true,
12
+ note: 'planner-assisted docs-only context preparation',
13
+ }),
14
+ 'ai plan': Object.freeze({
15
+ withPlanner: true,
16
+ interactive: true,
17
+ review: true,
18
+ note: 'planner draft generation and human approval workflow',
19
+ }),
20
+ 'spec create': Object.freeze({
21
+ withPlanner: true,
22
+ interactive: true,
23
+ review: true,
24
+ note: 'spec generation from approved planner output',
25
+ }),
26
+ 'ai pr': Object.freeze({
27
+ withPlanner: false,
28
+ interactive: true,
29
+ review: true,
30
+ note: 'PR body review and interactive PR inputs',
31
+ }),
32
+ });
33
+
34
+ function formatError(message) {
35
+ return `create-quiver: ${message}`;
36
+ }
37
+
38
+ function normalizeCommandKey(value) {
39
+ return String(value || '').trim().replace(/\s+/g, ' ');
40
+ }
41
+
42
+ function resolveUxCommandKey(args = {}) {
43
+ if (args.mode === 'ai') {
44
+ const command = args.aiCommand || '';
45
+ if (command === 'specs' || command === 'slices' || command === 'trace' || command === 'active-slice') {
46
+ return normalizeCommandKey(`ai ${command} ${args.aiSecondaryCommand || ''}`);
47
+ }
48
+ if (command === 'agent') {
49
+ return normalizeCommandKey(`ai ${command} ${args.aiAgentCommand || ''}`);
50
+ }
51
+ if (command === 'run') {
52
+ return normalizeCommandKey(`ai ${command} ${args.aiRunCommand || ''}`);
53
+ }
54
+ return normalizeCommandKey(`ai ${command}`);
55
+ }
56
+
57
+ if (args.mode === 'spec') {
58
+ return normalizeCommandKey(`spec ${args.specCommand || ''}`);
59
+ }
60
+
61
+ if (args.mode === 'demo') {
62
+ return normalizeCommandKey(`demo ${args.demoCommand || ''} ${args.demoName || ''}`);
63
+ }
64
+
65
+ if (args.mode === 'evidence') {
66
+ return normalizeCommandKey(`evidence ${args.evidenceCommand || ''}`);
67
+ }
68
+
69
+ return normalizeCommandKey(args.mode);
70
+ }
71
+
72
+ function getUxFlagSupport(commandKey) {
73
+ return UX_FLAG_MATRIX[normalizeCommandKey(commandKey)] || Object.freeze({
74
+ withPlanner: false,
75
+ interactive: false,
76
+ review: false,
77
+ note: 'no UX flag support',
78
+ });
79
+ }
80
+
81
+ function getRequestedUxFlags(args = {}) {
82
+ return Object.keys(UX_FLAG_OPTIONS).filter((key) => args[key] === true);
83
+ }
84
+
85
+ function supportedFlagList(support) {
86
+ return Object.entries(UX_FLAG_OPTIONS)
87
+ .filter(([key]) => support[key] === true)
88
+ .map(([, flag]) => flag);
89
+ }
90
+
91
+ function explainAlternative(flag) {
92
+ if (flag === 'withPlanner') {
93
+ return 'Use a planner-capable command such as: npx create-quiver ai prepare-context --with-planner --dry-run';
94
+ }
95
+ if (flag === 'interactive') {
96
+ return 'Use explicit non-interactive flags for automation, or run a command that documents --interactive support.';
97
+ }
98
+ if (flag === 'review') {
99
+ return 'Use a command with persistent AI-generated output, then rerun with --review after inspecting the dry-run.';
100
+ }
101
+ return 'Run: npx create-quiver --help';
102
+ }
103
+
104
+ function buildUnsupportedFlagError(commandKey, flag, support) {
105
+ const flagName = UX_FLAG_OPTIONS[flag];
106
+ const supported = supportedFlagList(support);
107
+ const supportedText = supported.length > 0 ? supported.join(', ') : 'none';
108
+
109
+ return new Error(formatError([
110
+ `UX flag ${flagName} is not supported by '${commandKey}'.`,
111
+ `Supported UX flags for '${commandKey}': ${supportedText}.`,
112
+ explainAlternative(flag),
113
+ ].join('\n')));
114
+ }
115
+
116
+ function validateUxFlagCombinations(args = {}, commandKey = resolveUxCommandKey(args)) {
117
+ if (args.json && args.interactive) {
118
+ throw new Error(formatError(`incompatible UX flags for '${commandKey}': --json cannot be combined with --interactive because JSON output must stay machine-readable.`));
119
+ }
120
+ if (args.json && args.review) {
121
+ throw new Error(formatError(`incompatible UX flags for '${commandKey}': --json cannot be combined with --review because editor review is a human-only flow.`));
122
+ }
123
+ }
124
+
125
+ function validateUxFlags(args = {}) {
126
+ const commandKey = resolveUxCommandKey(args);
127
+ validateUxFlagCombinations(args, commandKey);
128
+
129
+ const support = getUxFlagSupport(commandKey);
130
+ for (const flag of getRequestedUxFlags(args)) {
131
+ if (support[flag] !== true) {
132
+ throw buildUnsupportedFlagError(commandKey, flag, support);
133
+ }
134
+ }
135
+
136
+ return {
137
+ commandKey,
138
+ requested: getRequestedUxFlags(args).map((flag) => UX_FLAG_OPTIONS[flag]),
139
+ supported: supportedFlagList(support),
140
+ };
141
+ }
142
+
143
+ module.exports = {
144
+ UX_FLAG_MATRIX,
145
+ UX_FLAG_OPTIONS,
146
+ getRequestedUxFlags,
147
+ getUxFlagSupport,
148
+ resolveUxCommandKey,
149
+ validateUxFlagCombinations,
150
+ validateUxFlags,
151
+ };
@@ -0,0 +1,130 @@
1
+ const { resolveTheme, isTruthy } = require('./theme');
2
+
3
+ function resolveUxMode(options = {}, env = process.env, io = {}) {
4
+ const isTTY = {
5
+ stdin: options.stdinIsTTY ?? Boolean((io.input || process.stdin).isTTY),
6
+ stdout: options.stdoutIsTTY ?? Boolean((io.output || process.stdout).isTTY),
7
+ stderr: options.stderrIsTTY ?? Boolean((io.error || process.stderr).isTTY),
8
+ };
9
+ const json = options.json === true;
10
+ const ci = isTruthy(env.CI);
11
+ const noColor = options.noColor === true || Boolean(env.NO_COLOR);
12
+ const interactive = options.interactive === true;
13
+ const decoration = !json && !ci && isTTY.stdout;
14
+ const usePrompts = decoration && interactive && isTTY.stdin;
15
+ const useSpinners = decoration && options.spinner !== false;
16
+ const theme = resolveTheme({ ...options, json, noColor }, env, {
17
+ stdout: isTTY.stdout,
18
+ });
19
+
20
+ return {
21
+ ci,
22
+ decoration,
23
+ interactive,
24
+ isTTY,
25
+ json,
26
+ noColor,
27
+ theme,
28
+ usePrompts,
29
+ useSpinners,
30
+ };
31
+ }
32
+
33
+ async function loadClack(injected) {
34
+ if (injected) return injected;
35
+ return import('@clack/prompts');
36
+ }
37
+
38
+ function createUx(options = {}) {
39
+ const env = options.env || process.env;
40
+ const io = {
41
+ input: options.input || process.stdin,
42
+ output: options.output || process.stdout,
43
+ error: options.error || process.stderr,
44
+ };
45
+ const mode = resolveUxMode(options, env, io);
46
+ const write = options.write || ((text) => io.output.write(text));
47
+
48
+ function line(text = '') {
49
+ write(`${text}\n`);
50
+ }
51
+
52
+ function heading(text) {
53
+ if (mode.json) return;
54
+ if (!mode.decoration) {
55
+ line(text);
56
+ return;
57
+ }
58
+ const prefix = mode.theme.symbols.start;
59
+ line(mode.theme.status('planner', `${prefix} ${text}`));
60
+ }
61
+
62
+ function writeStatus(status, text) {
63
+ if (mode.json) return;
64
+ if (!mode.decoration) {
65
+ line(text);
66
+ return;
67
+ }
68
+ const symbol = mode.theme.symbols[status] || mode.theme.symbols.bullet;
69
+ const body = mode.theme.status(status, `${symbol} ${text}`);
70
+ line(body);
71
+ }
72
+
73
+ async function withSpinner(message, task, spinnerOptions = {}) {
74
+ if (!mode.useSpinners) {
75
+ if (!mode.json && spinnerOptions.echo !== false) {
76
+ if (mode.decoration) {
77
+ writeStatus('info', message);
78
+ } else {
79
+ line(message);
80
+ }
81
+ }
82
+ return task();
83
+ }
84
+
85
+ const clack = await loadClack(options.prompts);
86
+ const spinner = clack.spinner();
87
+ spinner.start(message);
88
+ try {
89
+ const result = await task();
90
+ spinner.stop(spinnerOptions.successMessage || message);
91
+ return result;
92
+ } catch (error) {
93
+ spinner.stop(spinnerOptions.failureMessage || `Failed: ${message}`, 1);
94
+ throw error;
95
+ }
96
+ }
97
+
98
+ async function promptConfirm(message, promptOptions = {}) {
99
+ if (!mode.usePrompts) {
100
+ throw new Error('create-quiver: interactive confirmation requires an interactive TTY. Pass the equivalent flags or rerun with --interactive.');
101
+ }
102
+ if (options.promptConfirm) {
103
+ return options.promptConfirm(message, promptOptions);
104
+ }
105
+ const clack = await loadClack(options.prompts);
106
+ const result = await clack.confirm({
107
+ message,
108
+ initialValue: promptOptions.initialValue === true,
109
+ });
110
+ if (clack.isCancel && clack.isCancel(result)) {
111
+ throw new Error('create-quiver: interactive confirmation canceled.');
112
+ }
113
+ return result === true;
114
+ }
115
+
116
+ return {
117
+ heading,
118
+ line,
119
+ mode,
120
+ promptConfirm,
121
+ theme: mode.theme,
122
+ withSpinner,
123
+ writeStatus,
124
+ };
125
+ }
126
+
127
+ module.exports = {
128
+ createUx,
129
+ resolveUxMode,
130
+ };