create-quiver 0.17.1 → 0.17.3

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 (119) hide show
  1. package/ACTIVE_SLICES.md +43 -0
  2. package/CHANGELOG.md +8 -0
  3. package/auditoria-ux-ui-performance-documentacion.md +705 -0
  4. package/copys-landing-page.md +244 -0
  5. package/docs/CLI_UX_GUIDE.md +13 -3
  6. package/docs/TROUBLESHOOTING.md +94 -0
  7. package/docs/reference/commands.md +68 -4
  8. package/docs/workflows/existing-project-ai-quiver-setup.md +552 -0
  9. package/docs/workflows/existing-project.md +36 -0
  10. package/package.json +1 -1
  11. package/specs/quiver-v43-cli-i18n-audit-release-readiness/command-language-mode-matrix.json +5 -0
  12. package/specs/quiver-v53-reliable-deep-project-analysis/EVIDENCE_REPORT.md +151 -0
  13. package/specs/quiver-v53-reliable-deep-project-analysis/EXECUTION_PLAN.md +50 -0
  14. package/specs/quiver-v53-reliable-deep-project-analysis/SPEC.md +207 -0
  15. package/specs/quiver-v53-reliable-deep-project-analysis/STATUS.md +32 -0
  16. package/specs/quiver-v53-reliable-deep-project-analysis/pr.md +73 -0
  17. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-00-analysis-run-contract/CLOSURE_BRIEF.md +32 -0
  18. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-00-analysis-run-contract/EXECUTION_BRIEF.md +63 -0
  19. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-00-analysis-run-contract/slice.json +70 -0
  20. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-01-provider-fixture-harness/CLOSURE_BRIEF.md +38 -0
  21. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-01-provider-fixture-harness/EXECUTION_BRIEF.md +60 -0
  22. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-01-provider-fixture-harness/slice.json +74 -0
  23. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-02-safe-context-boundary/CLOSURE_BRIEF.md +33 -0
  24. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-02-safe-context-boundary/EXECUTION_BRIEF.md +68 -0
  25. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-02-safe-context-boundary/slice.json +85 -0
  26. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-03-schema-error-grouping/CLOSURE_BRIEF.md +34 -0
  27. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-03-schema-error-grouping/EXECUTION_BRIEF.md +65 -0
  28. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-03-schema-error-grouping/slice.json +80 -0
  29. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-04-safe-repair-layer/CLOSURE_BRIEF.md +34 -0
  30. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-04-safe-repair-layer/EXECUTION_BRIEF.md +63 -0
  31. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-04-safe-repair-layer/slice.json +80 -0
  32. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-05-controlled-retry-layer/CLOSURE_BRIEF.md +34 -0
  33. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-05-controlled-retry-layer/EXECUTION_BRIEF.md +65 -0
  34. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-05-controlled-retry-layer/slice.json +81 -0
  35. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-06-audit-review-transaction/CLOSURE_BRIEF.md +35 -0
  36. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-06-audit-review-transaction/EXECUTION_BRIEF.md +71 -0
  37. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-06-audit-review-transaction/slice.json +88 -0
  38. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-07-semantic-validation-docs/CLOSURE_BRIEF.md +32 -0
  39. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-07-semantic-validation-docs/EXECUTION_BRIEF.md +66 -0
  40. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-07-semantic-validation-docs/slice.json +86 -0
  41. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-08-structural-map-hardening/CLOSURE_BRIEF.md +34 -0
  42. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-08-structural-map-hardening/EXECUTION_BRIEF.md +67 -0
  43. package/specs/quiver-v53-reliable-deep-project-analysis/slices/slice-08-structural-map-hardening/slice.json +81 -0
  44. package/specs/quiver-v54-deep-project-analysis-hardening/EVIDENCE_REPORT.md +53 -0
  45. package/specs/quiver-v54-deep-project-analysis-hardening/EXECUTION_PLAN.md +55 -0
  46. package/specs/quiver-v54-deep-project-analysis-hardening/SPEC.md +184 -0
  47. package/specs/quiver-v54-deep-project-analysis-hardening/STATUS.md +34 -0
  48. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-01-contract-regression-harness/CLOSURE_BRIEF.md +10 -0
  49. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-01-contract-regression-harness/EXECUTION_BRIEF.md +54 -0
  50. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-01-contract-regression-harness/slice.json +72 -0
  51. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-02-safe-discovery-sampling/CLOSURE_BRIEF.md +11 -0
  52. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-02-safe-discovery-sampling/EXECUTION_BRIEF.md +53 -0
  53. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-02-safe-discovery-sampling/slice.json +69 -0
  54. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-03-run-lifecycle-cli-io/CLOSURE_BRIEF.md +10 -0
  55. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-03-run-lifecycle-cli-io/EXECUTION_BRIEF.md +53 -0
  56. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-03-run-lifecycle-cli-io/slice.json +69 -0
  57. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-04-artifact-store-redaction/CLOSURE_BRIEF.md +10 -0
  58. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-04-artifact-store-redaction/EXECUTION_BRIEF.md +54 -0
  59. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-04-artifact-store-redaction/slice.json +73 -0
  60. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-05-path-aware-repair-engine/CLOSURE_BRIEF.md +10 -0
  61. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-05-path-aware-repair-engine/EXECUTION_BRIEF.md +57 -0
  62. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-05-path-aware-repair-engine/slice.json +72 -0
  63. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-06-retry-controller/CLOSURE_BRIEF.md +9 -0
  64. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-06-retry-controller/EXECUTION_BRIEF.md +52 -0
  65. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-06-retry-controller/slice.json +70 -0
  66. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-07-final-validation-review-write-gate/CLOSURE_BRIEF.md +10 -0
  67. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-07-final-validation-review-write-gate/EXECUTION_BRIEF.md +57 -0
  68. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-07-final-validation-review-write-gate/slice.json +75 -0
  69. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-08-doctor-agents-guidance/CLOSURE_BRIEF.md +9 -0
  70. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-08-doctor-agents-guidance/EXECUTION_BRIEF.md +51 -0
  71. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-08-doctor-agents-guidance/slice.json +66 -0
  72. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-09-release-smoke-rollback-docs/CLOSURE_BRIEF.md +10 -0
  73. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-09-release-smoke-rollback-docs/EXECUTION_BRIEF.md +54 -0
  74. package/specs/quiver-v54-deep-project-analysis-hardening/slices/slice-09-release-smoke-rollback-docs/slice.json +69 -0
  75. package/specs/quiver-v55-analyze-project-doc-apply-ux/EVIDENCE_REPORT.md +209 -0
  76. package/specs/quiver-v55-analyze-project-doc-apply-ux/EXECUTION_PLAN.md +49 -0
  77. package/specs/quiver-v55-analyze-project-doc-apply-ux/SPEC.md +276 -0
  78. package/specs/quiver-v55-analyze-project-doc-apply-ux/STATUS.md +29 -0
  79. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-01-cli-proposal-contract/CLOSURE_BRIEF.md +47 -0
  80. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-01-cli-proposal-contract/EXECUTION_BRIEF.md +67 -0
  81. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-01-cli-proposal-contract/pr.md +129 -0
  82. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-01-cli-proposal-contract/slice.json +82 -0
  83. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-02-save-proposal-flow/CLOSURE_BRIEF.md +39 -0
  84. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-02-save-proposal-flow/EXECUTION_BRIEF.md +61 -0
  85. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-02-save-proposal-flow/pr.md +116 -0
  86. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-02-save-proposal-flow/slice.json +69 -0
  87. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-03-noninteractive-apply-engine/CLOSURE_BRIEF.md +36 -0
  88. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-03-noninteractive-apply-engine/EXECUTION_BRIEF.md +62 -0
  89. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-03-noninteractive-apply-engine/pr.md +132 -0
  90. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-03-noninteractive-apply-engine/slice.json +76 -0
  91. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-04-interactive-apply-ux/CLOSURE_BRIEF.md +47 -0
  92. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-04-interactive-apply-ux/EXECUTION_BRIEF.md +70 -0
  93. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-04-interactive-apply-ux/pr.md +141 -0
  94. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-04-interactive-apply-ux/slice.json +72 -0
  95. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-05-apply-saved-proposal/CLOSURE_BRIEF.md +42 -0
  96. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-05-apply-saved-proposal/EXECUTION_BRIEF.md +61 -0
  97. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-05-apply-saved-proposal/pr.md +129 -0
  98. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-05-apply-saved-proposal/slice.json +67 -0
  99. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-06-i18n-docs-release-smoke/CLOSURE_BRIEF.md +38 -0
  100. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-06-i18n-docs-release-smoke/EXECUTION_BRIEF.md +64 -0
  101. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-06-i18n-docs-release-smoke/pr.md +123 -0
  102. package/specs/quiver-v55-analyze-project-doc-apply-ux/slices/slice-06-i18n-docs-release-smoke/slice.json +76 -0
  103. package/src/create-quiver/commands/ai.js +989 -136
  104. package/src/create-quiver/index.js +83 -1
  105. package/src/create-quiver/lib/ai/analyze-project-apply.js +269 -0
  106. package/src/create-quiver/lib/ai/analyze-project-discovery.js +213 -2
  107. package/src/create-quiver/lib/ai/analyze-project-docs.js +3 -1
  108. package/src/create-quiver/lib/ai/analyze-project-interactive.js +241 -0
  109. package/src/create-quiver/lib/ai/analyze-project-parser.js +1 -0
  110. package/src/create-quiver/lib/ai/analyze-project-prompts.js +130 -0
  111. package/src/create-quiver/lib/ai/analyze-project-proposal.js +582 -0
  112. package/src/create-quiver/lib/ai/analyze-project-repair.js +402 -0
  113. package/src/create-quiver/lib/ai/analyze-project-sampling.js +31 -4
  114. package/src/create-quiver/lib/ai/analyze-project-validation.js +178 -8
  115. package/src/create-quiver/lib/ai/artifacts.js +83 -2
  116. package/src/create-quiver/lib/cli/ux-flags.js +6 -0
  117. package/src/create-quiver/lib/doctor.js +107 -12
  118. package/src/create-quiver/lib/i18n/messages/en.js +33 -0
  119. package/src/create-quiver/lib/i18n/messages/es.js +34 -0
@@ -0,0 +1,582 @@
1
+ const crypto = require('node:crypto');
2
+ const fs = require('node:fs');
3
+ const path = require('node:path');
4
+
5
+ const { z } = require('zod');
6
+
7
+ const { parseAnalyzeProjectDocProposal } = require('./analyze-project-docs');
8
+ const { validateProjectRelativePath } = require('../paths');
9
+
10
+ const ANALYZE_PROJECT_PROPOSAL_MANIFEST_SCHEMA_VERSION = 1;
11
+ const ANALYZE_PROJECT_PROPOSAL_MANIFEST_KIND = 'quiver-analyze-project-doc-proposal';
12
+ const ANALYZE_PROJECT_WRITE_MANIFEST_SCHEMA_VERSION = 1;
13
+ const ANALYZE_PROJECT_WRITE_MANIFEST_KIND = 'quiver-analyze-project-doc-writes';
14
+
15
+ const PROPOSAL_ARTIFACT_FILENAMES = Object.freeze({
16
+ json: 'analyze-project-doc-proposal.json',
17
+ markdown: 'analyze-project-doc-proposal.md',
18
+ diff: 'analyze-project-doc-proposal.diff',
19
+ manifest: 'manifest.json',
20
+ });
21
+
22
+ function formatError(message) {
23
+ return `create-quiver: ${message}`;
24
+ }
25
+
26
+ class AnalyzeProjectProposalContractError extends Error {
27
+ constructor(message, issues = []) {
28
+ super(formatError(message));
29
+ this.name = 'AnalyzeProjectProposalContractError';
30
+ this.code = 'AI_ANALYZE_PROJECT_PROPOSAL_CONTRACT_INVALID';
31
+ this.issues = issues;
32
+ }
33
+ }
34
+
35
+ function normalizeArtifactPath(value, fieldName = 'artifact path') {
36
+ try {
37
+ return validateProjectRelativePath(value, fieldName);
38
+ } catch (error) {
39
+ throw new AnalyzeProjectProposalContractError(`${fieldName} is not a safe project-relative path`, [
40
+ { path: fieldName, issue: 'invalid-project-relative-path', message: error.message },
41
+ ]);
42
+ }
43
+ }
44
+
45
+ function toPosix(filePath) {
46
+ return String(filePath || '').replace(/\\/g, '/');
47
+ }
48
+
49
+ function toRelativePosix(root, filePath) {
50
+ return toPosix(path.relative(root, filePath));
51
+ }
52
+
53
+ function sha256(contents) {
54
+ return crypto.createHash('sha256').update(String(contents || ''), 'utf8').digest('hex');
55
+ }
56
+
57
+ function normalizeAnalyzeProjectProposalRunId(value) {
58
+ const runId = String(value || '').trim();
59
+ if (!runId) {
60
+ throw new AnalyzeProjectProposalContractError('analyze-project proposal run id is required', [
61
+ { path: 'run_id', issue: 'missing-run-id', message: 'run_id is required' },
62
+ ]);
63
+ }
64
+ if (runId === '.' || runId === '..' || runId.includes('/') || runId.includes('\\') || runId.includes('\0')) {
65
+ throw new AnalyzeProjectProposalContractError('analyze-project proposal run id is not safe', [
66
+ { path: 'run_id', issue: 'unsafe-run-id', message: 'run_id must be a single path segment' },
67
+ ]);
68
+ }
69
+ if (!/^[A-Za-z0-9._-]+$/.test(runId)) {
70
+ throw new AnalyzeProjectProposalContractError('analyze-project proposal run id is not safe', [
71
+ { path: 'run_id', issue: 'unsafe-run-id', message: 'run_id can contain only letters, numbers, dots, underscores, and hyphens' },
72
+ ]);
73
+ }
74
+ return runId;
75
+ }
76
+
77
+ function writeJsonFile(filePath, value) {
78
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
79
+ fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`);
80
+ }
81
+
82
+ function writeTextFile(filePath, value) {
83
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
84
+ fs.writeFileSync(filePath, String(value || ''));
85
+ }
86
+
87
+ function readJsonFile(filePath, description) {
88
+ if (!fs.existsSync(filePath)) {
89
+ throw new AnalyzeProjectProposalContractError(`missing analyze-project ${description}`, [
90
+ { path: toPosix(filePath), issue: 'missing-artifact', message: `${description} does not exist` },
91
+ ]);
92
+ }
93
+ try {
94
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
95
+ } catch (error) {
96
+ throw new AnalyzeProjectProposalContractError(`invalid analyze-project ${description}`, [
97
+ { path: toPosix(filePath), issue: 'malformed-json', message: error.message },
98
+ ]);
99
+ }
100
+ }
101
+
102
+ function artifactPathFromRef(ref) {
103
+ if (typeof ref === 'string') {
104
+ return normalizeArtifactPath(ref, 'artifact ref');
105
+ }
106
+ if (ref && typeof ref.path === 'string') {
107
+ return normalizeArtifactPath(ref.path, 'artifact ref');
108
+ }
109
+ return null;
110
+ }
111
+
112
+ function relativePathSchema(fieldName) {
113
+ return z.string().trim().min(1).transform((value) => normalizeArtifactPath(value, fieldName));
114
+ }
115
+
116
+ const proposalManifestSchema = z.object({
117
+ schema_version: z.literal(ANALYZE_PROJECT_PROPOSAL_MANIFEST_SCHEMA_VERSION),
118
+ kind: z.literal(ANALYZE_PROJECT_PROPOSAL_MANIFEST_KIND),
119
+ run_id: z.string().trim().min(1),
120
+ created_at: z.string().trim().min(1),
121
+ language: z.enum(['en', 'es']),
122
+ provider: z.string().trim().min(1).nullable(),
123
+ proposal_json: relativePathSchema('proposal_json'),
124
+ proposal_markdown: relativePathSchema('proposal_markdown'),
125
+ proposal_diff: relativePathSchema('proposal_diff'),
126
+ selected_context_manifest: relativePathSchema('selected_context_manifest'),
127
+ repair_manifest: relativePathSchema('repair_manifest').nullable(),
128
+ doc_paths: z.array(relativePathSchema('doc_paths')).default([]),
129
+ doc_before_hashes: z.record(z.string().trim().min(1), z.string().trim().min(1).nullable()).default({}),
130
+ proposal_sha256: z.string().trim().min(1),
131
+ events: z.array(z.record(z.string(), z.unknown())).default([]),
132
+ }).strict();
133
+
134
+ const writeManifestActionSchema = z.object({
135
+ path: relativePathSchema('actions.path'),
136
+ action: z.enum(['create', 'update', 'skip']),
137
+ before_sha256: z.string().trim().min(1).nullable(),
138
+ after_sha256: z.string().trim().min(1).nullable(),
139
+ snapshot_path: relativePathSchema('actions.snapshot_path').nullable(),
140
+ dirty: z.boolean().default(false),
141
+ status: z.enum(['planned', 'written', 'skipped', 'failed']).default('planned'),
142
+ }).strict();
143
+
144
+ const writeManifestSchema = z.object({
145
+ schema_version: z.literal(ANALYZE_PROJECT_WRITE_MANIFEST_SCHEMA_VERSION),
146
+ kind: z.literal(ANALYZE_PROJECT_WRITE_MANIFEST_KIND),
147
+ run_id: z.string().trim().min(1),
148
+ created_at: z.string().trim().min(1),
149
+ proposal_manifest: relativePathSchema('proposal_manifest'),
150
+ snapshot_root: relativePathSchema('snapshot_root').nullable(),
151
+ actions: z.array(writeManifestActionSchema).default([]),
152
+ validation: z.object({
153
+ ok: z.boolean(),
154
+ strict: z.boolean().default(false),
155
+ errors: z.array(z.record(z.string(), z.unknown())).default([]),
156
+ warnings: z.array(z.record(z.string(), z.unknown())).default([]),
157
+ }).strict(),
158
+ partial_write: z.boolean().default(false),
159
+ events: z.array(z.record(z.string(), z.unknown())).default([]),
160
+ }).strict();
161
+
162
+ function mapZodIssues(error) {
163
+ return error.issues.map((issue) => ({
164
+ path: issue.path.length > 0 ? issue.path.join('.') : null,
165
+ issue: issue.code,
166
+ message: issue.message,
167
+ }));
168
+ }
169
+
170
+ function normalizeAnalyzeProjectProposalManifest(value) {
171
+ const parsed = proposalManifestSchema.safeParse(value);
172
+ if (!parsed.success) {
173
+ throw new AnalyzeProjectProposalContractError('analyze-project proposal manifest does not match the required schema', mapZodIssues(parsed.error));
174
+ }
175
+ return parsed.data;
176
+ }
177
+
178
+ function normalizeAnalyzeProjectWriteManifest(value) {
179
+ const parsed = writeManifestSchema.safeParse(value);
180
+ if (!parsed.success) {
181
+ throw new AnalyzeProjectProposalContractError('analyze-project write manifest does not match the required schema', mapZodIssues(parsed.error));
182
+ }
183
+ return parsed.data;
184
+ }
185
+
186
+ function buildAnalyzeProjectProposalArtifactPaths(runId) {
187
+ const safeRunId = normalizeAnalyzeProjectProposalRunId(runId);
188
+
189
+ const root = `.quiver/runs/${safeRunId}/proposal`;
190
+ return {
191
+ root,
192
+ proposal_json: `${root}/${PROPOSAL_ARTIFACT_FILENAMES.json}`,
193
+ proposal_markdown: `${root}/${PROPOSAL_ARTIFACT_FILENAMES.markdown}`,
194
+ proposal_diff: `${root}/${PROPOSAL_ARTIFACT_FILENAMES.diff}`,
195
+ manifest: `${root}/${PROPOSAL_ARTIFACT_FILENAMES.manifest}`,
196
+ write_manifest: `.quiver/runs/${safeRunId}/writes/analyze-project-doc-writes.json`,
197
+ };
198
+ }
199
+
200
+ function assertSavedProposalManifestMatchesRun(manifest, paths, runId) {
201
+ const issues = [];
202
+ if (manifest.run_id !== runId) {
203
+ issues.push({
204
+ path: 'manifest.run_id',
205
+ issue: 'run-id-mismatch',
206
+ message: `manifest run_id must match requested run id (${runId})`,
207
+ });
208
+ }
209
+ for (const [field, expected] of Object.entries({
210
+ proposal_json: paths.proposal_json,
211
+ proposal_markdown: paths.proposal_markdown,
212
+ proposal_diff: paths.proposal_diff,
213
+ })) {
214
+ if (manifest[field] !== expected) {
215
+ issues.push({
216
+ path: `manifest.${field}`,
217
+ issue: 'artifact-path-mismatch',
218
+ message: `${field} must match ${expected}`,
219
+ });
220
+ }
221
+ }
222
+ if (issues.length > 0) {
223
+ throw new AnalyzeProjectProposalContractError('saved analyze-project proposal manifest does not match the requested run', issues);
224
+ }
225
+ }
226
+
227
+ function assertSavedProposalArtifactsExist(repoRoot, manifest) {
228
+ const issues = [];
229
+ for (const [field, relativePath] of Object.entries({
230
+ proposal_json: manifest.proposal_json,
231
+ proposal_markdown: manifest.proposal_markdown,
232
+ proposal_diff: manifest.proposal_diff,
233
+ })) {
234
+ if (!fs.existsSync(path.join(repoRoot, relativePath))) {
235
+ issues.push({
236
+ path: field,
237
+ issue: 'missing-artifact',
238
+ message: `${relativePath} does not exist`,
239
+ });
240
+ }
241
+ }
242
+ if (issues.length > 0) {
243
+ throw new AnalyzeProjectProposalContractError('saved analyze-project proposal is missing required artifacts', issues);
244
+ }
245
+ }
246
+
247
+ function assertSavedProposalDocPathsMatchManifest(proposal, manifest) {
248
+ const proposalPaths = (proposal.docs || []).map((doc) => doc.path).sort();
249
+ const manifestPaths = [...(manifest.doc_paths || [])].sort();
250
+ if (JSON.stringify(proposalPaths) === JSON.stringify(manifestPaths)) {
251
+ return;
252
+ }
253
+ throw new AnalyzeProjectProposalContractError('saved analyze-project proposal docs do not match the proposal manifest', [
254
+ {
255
+ path: 'doc_paths',
256
+ issue: 'doc-paths-mismatch',
257
+ message: 'manual proposal edits may change content, but target doc paths must match the saved manifest',
258
+ expected: manifestPaths,
259
+ actual: proposalPaths,
260
+ },
261
+ ]);
262
+ }
263
+
264
+ function readAnalyzeProjectSavedProposal(repoRoot, runId) {
265
+ const resolvedRunId = normalizeAnalyzeProjectProposalRunId(runId);
266
+ const paths = buildAnalyzeProjectProposalArtifactPaths(resolvedRunId);
267
+ const manifestPath = path.join(repoRoot, paths.manifest);
268
+ const manifest = normalizeAnalyzeProjectProposalManifest(readJsonFile(manifestPath, 'proposal manifest'));
269
+ assertSavedProposalManifestMatchesRun(manifest, paths, resolvedRunId);
270
+ assertSavedProposalArtifactsExist(repoRoot, manifest);
271
+
272
+ const proposalPath = path.join(repoRoot, manifest.proposal_json);
273
+ let proposal;
274
+ try {
275
+ proposal = parseAnalyzeProjectDocProposal(fs.readFileSync(proposalPath, 'utf8'));
276
+ } catch (error) {
277
+ throw new AnalyzeProjectProposalContractError('saved analyze-project proposal JSON is invalid', error.issues || [{
278
+ path: manifest.proposal_json,
279
+ issue: error.code || 'invalid-proposal',
280
+ message: error.message,
281
+ }]);
282
+ }
283
+ assertSavedProposalDocPathsMatchManifest(proposal, manifest);
284
+
285
+ const proposalJson = `${JSON.stringify(proposal, null, 2)}\n`;
286
+ const proposalHash = sha256(proposalJson);
287
+ const proposalEdited = proposalHash !== manifest.proposal_sha256;
288
+
289
+ return {
290
+ run_id: resolvedRunId,
291
+ proposal,
292
+ proposal_sha256: proposalHash,
293
+ proposal_edited: proposalEdited,
294
+ manifest,
295
+ artifacts: {
296
+ root: paths.root,
297
+ proposal_json: manifest.proposal_json,
298
+ proposal_markdown: manifest.proposal_markdown,
299
+ proposal_diff: manifest.proposal_diff,
300
+ manifest: paths.manifest,
301
+ proposal_sha256: proposalHash,
302
+ doc_paths: manifest.doc_paths,
303
+ changed_docs: manifest.doc_paths,
304
+ manifest_data: manifest,
305
+ files: [
306
+ manifest.proposal_json,
307
+ manifest.proposal_markdown,
308
+ manifest.proposal_diff,
309
+ paths.manifest,
310
+ ],
311
+ },
312
+ };
313
+ }
314
+
315
+ function findLatestAnalyzeProjectSavedProposalRun(repoRoot) {
316
+ const runsRoot = path.join(repoRoot, '.quiver', 'runs');
317
+ if (!fs.existsSync(runsRoot)) {
318
+ return null;
319
+ }
320
+
321
+ const candidates = [];
322
+ for (const entry of fs.readdirSync(runsRoot)) {
323
+ let runId;
324
+ try {
325
+ runId = normalizeAnalyzeProjectProposalRunId(entry);
326
+ } catch {
327
+ continue;
328
+ }
329
+ const manifestPath = path.join(runsRoot, runId, 'proposal', PROPOSAL_ARTIFACT_FILENAMES.manifest);
330
+ if (!fs.existsSync(manifestPath)) {
331
+ continue;
332
+ }
333
+ let timestamp = 0;
334
+ try {
335
+ const manifest = normalizeAnalyzeProjectProposalManifest(readJsonFile(manifestPath, 'proposal manifest'));
336
+ timestamp = Date.parse(manifest.created_at) || fs.statSync(manifestPath).mtimeMs;
337
+ } catch {
338
+ timestamp = fs.statSync(manifestPath).mtimeMs;
339
+ }
340
+ candidates.push({ runId, timestamp });
341
+ }
342
+
343
+ candidates.sort((a, b) => {
344
+ if (b.timestamp !== a.timestamp) {
345
+ return b.timestamp - a.timestamp;
346
+ }
347
+ return b.runId.localeCompare(a.runId);
348
+ });
349
+
350
+ return candidates[0]?.runId || null;
351
+ }
352
+
353
+ function formatAnalyzeProjectFullDiff(writePlan = []) {
354
+ const lines = [];
355
+ for (const item of writePlan) {
356
+ if (item.action === 'skip') {
357
+ continue;
358
+ }
359
+ lines.push(`--- ${item.path} (current)`);
360
+ lines.push(`+++ ${item.path} (proposed)`);
361
+ const currentLines = String(item.currentContent || '').split('\n');
362
+ const proposedLines = String(item.proposedContent || '').split('\n');
363
+ for (const line of currentLines) {
364
+ lines.push(`- ${line}`);
365
+ }
366
+ for (const line of proposedLines) {
367
+ lines.push(`+ ${line}`);
368
+ }
369
+ }
370
+ return `${(lines.length > 0 ? lines : ['- no changes']).join('\n')}\n`;
371
+ }
372
+
373
+ function formatAnalyzeProjectProposalSummary({ runId, provider, proposal, writePlan, artifacts } = {}) {
374
+ const changed = (writePlan || []).filter((item) => item.action !== 'skip');
375
+ const dirty = changed.filter((item) => item.dirty);
376
+ const docs = Array.isArray(proposal?.docs) ? proposal.docs : [];
377
+ const lines = [
378
+ '# Analyze Project Documentation Proposal',
379
+ '',
380
+ `Run: ${runId}`,
381
+ `Provider: ${provider || 'unknown'}`,
382
+ `Proposed docs: ${docs.length}`,
383
+ `Docs with changes: ${changed.length}`,
384
+ `Dirty existing docs: ${dirty.length}`,
385
+ '',
386
+ '## Files',
387
+ ];
388
+
389
+ for (const item of writePlan || []) {
390
+ lines.push(`- ${item.path}: ${item.action}${item.dirty ? ' (dirty existing doc)' : ''}`);
391
+ }
392
+ if (!Array.isArray(writePlan) || writePlan.length === 0) {
393
+ lines.push('- none');
394
+ }
395
+
396
+ lines.push('', '## Artifacts');
397
+ lines.push(`- Proposal JSON: ${artifacts.proposal_json}`);
398
+ lines.push(`- Diff: ${artifacts.proposal_diff}`);
399
+ lines.push(`- Manifest: ${artifacts.manifest}`);
400
+ lines.push('', 'This summary intentionally omits full proposed document contents. Use the JSON or diff artifact for details.');
401
+
402
+ return `${lines.join('\n')}\n`;
403
+ }
404
+
405
+ function writeAnalyzeProjectProposalArtifacts(repoRoot, options = {}) {
406
+ const runId = String(options.runId || '').trim();
407
+ if (!runId) {
408
+ throw new AnalyzeProjectProposalContractError('analyze-project proposal artifacts require a run id', [
409
+ { path: 'run_id', issue: 'missing-run-id', message: 'run_id is required' },
410
+ ]);
411
+ }
412
+
413
+ const now = options.now instanceof Date ? options.now : new Date(options.now || Date.now());
414
+ const provider = options.provider ? String(options.provider) : null;
415
+ const language = options.language === 'es' ? 'es' : 'en';
416
+ const proposal = options.proposal || { schema_version: 1, kind: 'quiver-analyze-project-doc-proposal', summary: '', docs: [] };
417
+ const writePlan = Array.isArray(options.writePlan) ? options.writePlan : [];
418
+ const paths = buildAnalyzeProjectProposalArtifactPaths(runId);
419
+ const proposalJson = `${JSON.stringify(proposal, null, 2)}\n`;
420
+ const proposalHash = sha256(proposalJson);
421
+ const docBeforeHashes = {};
422
+
423
+ for (const item of writePlan) {
424
+ docBeforeHashes[item.path] = item.before_sha256 || null;
425
+ }
426
+
427
+ const selectedContextPath = artifactPathFromRef(options.selectedContextManifest)
428
+ || `.quiver/runs/${runId}/context/selected-context.json`;
429
+ const repairPath = artifactPathFromRef(options.repairManifest);
430
+ const manifest = normalizeAnalyzeProjectProposalManifest({
431
+ schema_version: ANALYZE_PROJECT_PROPOSAL_MANIFEST_SCHEMA_VERSION,
432
+ kind: ANALYZE_PROJECT_PROPOSAL_MANIFEST_KIND,
433
+ run_id: runId,
434
+ created_at: now.toISOString(),
435
+ language,
436
+ provider,
437
+ proposal_json: paths.proposal_json,
438
+ proposal_markdown: paths.proposal_markdown,
439
+ proposal_diff: paths.proposal_diff,
440
+ selected_context_manifest: selectedContextPath,
441
+ repair_manifest: repairPath,
442
+ doc_paths: writePlan.map((item) => item.path),
443
+ doc_before_hashes: docBeforeHashes,
444
+ proposal_sha256: proposalHash,
445
+ events: [
446
+ {
447
+ type: 'proposal-saved',
448
+ at: now.toISOString(),
449
+ doc_count: writePlan.length,
450
+ changed_count: writePlan.filter((item) => item.action !== 'skip').length,
451
+ },
452
+ ...(Array.isArray(options.events) ? options.events : []),
453
+ ],
454
+ });
455
+
456
+ const absolutePaths = {
457
+ proposalJson: path.join(repoRoot, paths.proposal_json),
458
+ proposalMarkdown: path.join(repoRoot, paths.proposal_markdown),
459
+ proposalDiff: path.join(repoRoot, paths.proposal_diff),
460
+ manifest: path.join(repoRoot, paths.manifest),
461
+ };
462
+ const diff = formatAnalyzeProjectFullDiff(writePlan);
463
+ const summary = formatAnalyzeProjectProposalSummary({
464
+ runId,
465
+ provider,
466
+ proposal,
467
+ writePlan,
468
+ artifacts: paths,
469
+ });
470
+
471
+ writeTextFile(absolutePaths.proposalJson, proposalJson);
472
+ writeTextFile(absolutePaths.proposalMarkdown, summary);
473
+ writeTextFile(absolutePaths.proposalDiff, diff);
474
+ writeJsonFile(absolutePaths.manifest, manifest);
475
+
476
+ return {
477
+ root: paths.root,
478
+ proposal_json: paths.proposal_json,
479
+ proposal_markdown: paths.proposal_markdown,
480
+ proposal_diff: paths.proposal_diff,
481
+ manifest: paths.manifest,
482
+ proposal_sha256: proposalHash,
483
+ doc_paths: manifest.doc_paths,
484
+ changed_docs: writePlan.filter((item) => item.action !== 'skip').map((item) => item.path),
485
+ manifest_data: manifest,
486
+ files: [
487
+ paths.proposal_json,
488
+ paths.proposal_markdown,
489
+ paths.proposal_diff,
490
+ paths.manifest,
491
+ ],
492
+ absolute_files: Object.values(absolutePaths).map((filePath) => toRelativePosix(repoRoot, filePath)),
493
+ };
494
+ }
495
+
496
+ function writeAnalyzeProjectWriteManifest(repoRoot, options = {}) {
497
+ const runId = String(options.runId || '').trim();
498
+ if (!runId) {
499
+ throw new AnalyzeProjectProposalContractError('analyze-project write manifest requires a run id', [
500
+ { path: 'run_id', issue: 'missing-run-id', message: 'run_id is required' },
501
+ ]);
502
+ }
503
+
504
+ const now = options.now instanceof Date ? options.now : new Date(options.now || Date.now());
505
+ const paths = buildAnalyzeProjectProposalArtifactPaths(runId);
506
+ const writePlan = Array.isArray(options.writePlan) ? options.writePlan : [];
507
+ const writtenDocs = new Set(Array.isArray(options.writtenDocs) ? options.writtenDocs : []);
508
+ const snapshotEntries = new Map(
509
+ (options.snapshot?.entries || []).map((entry) => [entry.path, entry]),
510
+ );
511
+ const actionStatus = options.status || 'planned';
512
+ const actions = writePlan.map((item) => {
513
+ const snapshotEntry = snapshotEntries.get(item.path);
514
+ let status = item.action === 'skip' ? 'skipped' : actionStatus;
515
+ if (actionStatus === 'completed') {
516
+ status = item.action === 'skip' ? 'skipped' : writtenDocs.has(item.path) ? 'written' : 'failed';
517
+ }
518
+
519
+ return {
520
+ path: item.path,
521
+ action: item.action,
522
+ before_sha256: item.before_sha256 || null,
523
+ after_sha256: item.after_sha256 || null,
524
+ snapshot_path: snapshotEntry?.snapshot_path || null,
525
+ dirty: item.dirty === true,
526
+ status,
527
+ };
528
+ });
529
+ const validation = options.validation || {
530
+ ok: false,
531
+ strict: options.strict === true,
532
+ errors: [],
533
+ warnings: [],
534
+ };
535
+ const manifest = normalizeAnalyzeProjectWriteManifest({
536
+ schema_version: ANALYZE_PROJECT_WRITE_MANIFEST_SCHEMA_VERSION,
537
+ kind: ANALYZE_PROJECT_WRITE_MANIFEST_KIND,
538
+ run_id: runId,
539
+ created_at: now.toISOString(),
540
+ proposal_manifest: options.proposalManifest || paths.manifest,
541
+ snapshot_root: options.snapshot?.root || null,
542
+ actions,
543
+ validation: {
544
+ ok: validation.ok === true,
545
+ strict: validation.strict === true,
546
+ errors: Array.isArray(validation.errors) ? validation.errors : [],
547
+ warnings: Array.isArray(validation.warnings) ? validation.warnings : [],
548
+ },
549
+ partial_write: options.partialWrite === true,
550
+ events: Array.isArray(options.events) ? options.events : [],
551
+ });
552
+
553
+ const manifestPath = path.join(repoRoot, paths.write_manifest);
554
+ writeJsonFile(manifestPath, manifest);
555
+
556
+ return {
557
+ path: paths.write_manifest,
558
+ root: path.dirname(paths.write_manifest).split(path.sep).join('/'),
559
+ manifest_data: manifest,
560
+ };
561
+ }
562
+
563
+ module.exports = {
564
+ ANALYZE_PROJECT_PROPOSAL_MANIFEST_KIND,
565
+ ANALYZE_PROJECT_PROPOSAL_MANIFEST_SCHEMA_VERSION,
566
+ ANALYZE_PROJECT_WRITE_MANIFEST_KIND,
567
+ ANALYZE_PROJECT_WRITE_MANIFEST_SCHEMA_VERSION,
568
+ AnalyzeProjectProposalContractError,
569
+ PROPOSAL_ARTIFACT_FILENAMES,
570
+ buildAnalyzeProjectProposalArtifactPaths,
571
+ findLatestAnalyzeProjectSavedProposalRun,
572
+ formatAnalyzeProjectFullDiff,
573
+ formatAnalyzeProjectProposalSummary,
574
+ normalizeAnalyzeProjectProposalRunId,
575
+ normalizeAnalyzeProjectProposalManifest,
576
+ normalizeAnalyzeProjectWriteManifest,
577
+ proposalManifestSchema,
578
+ readAnalyzeProjectSavedProposal,
579
+ writeAnalyzeProjectProposalArtifacts,
580
+ writeAnalyzeProjectWriteManifest,
581
+ writeManifestSchema,
582
+ };