create-quiver 0.15.4 → 0.17.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 (199) hide show
  1. package/.github/pull_request_template.md +17 -0
  2. package/.github/workflows/ci.yml +76 -0
  3. package/.markdown-link-check.json +15 -0
  4. package/.markdownlint.json +11 -0
  5. package/ARCHITECTURE.md +133 -0
  6. package/CHANGELOG.md +16 -0
  7. package/CONTRIBUTING.md +174 -9
  8. package/README.md +17 -2
  9. package/README_FOR_AI.md +4 -1
  10. package/ROADMAP.md +4 -0
  11. package/SECURITY.md +9 -2
  12. package/docs/AI_CONTEXT.md.es.template +3 -1
  13. package/docs/AI_CONTEXT.md.template +3 -1
  14. package/docs/CLI_UX_GUIDE.md +20 -1
  15. package/docs/COMMANDS.md.template +20 -6
  16. package/docs/GITFLOW_PR_GUIDE.md +73 -11
  17. package/docs/GITFLOW_PR_GUIDE.md.es.template +61 -2
  18. package/docs/GITFLOW_PR_GUIDE.md.template +72 -10
  19. package/docs/INDEX.md +6 -1
  20. package/docs/QUICK.md.template +2 -1
  21. package/docs/STANDARD.md.template +2 -1
  22. package/docs/WORKFLOW.md.es.template +3 -1
  23. package/docs/WORKFLOW.md.template +6 -4
  24. package/docs/getting-started/installation.md +7 -0
  25. package/docs/reference/commands.md +138 -6
  26. package/docs/reference/slice-schema.md +37 -0
  27. package/docs/schema/slice.schema.json +221 -0
  28. package/docs/specs/01-spec/01-slice/01-slice.json +36 -0
  29. package/docs/specs/01-spec/01-slice/01-slice.md +106 -0
  30. package/docs/specs/01-spec/01-slice/CLOSURE_BRIEF.md +80 -0
  31. package/docs/specs/01-spec/01-slice/EXECUTION_BRIEF.md +39 -0
  32. package/docs/specs/01-spec/02-slice/02-slice.json +36 -0
  33. package/docs/specs/01-spec/02-slice/02-slice.md +108 -0
  34. package/docs/specs/01-spec/02-slice/CLOSURE_BRIEF.md +88 -0
  35. package/docs/specs/01-spec/02-slice/EXECUTION_BRIEF.md +40 -0
  36. package/docs/specs/01-spec/03-slice/03-slice.json +36 -0
  37. package/docs/specs/01-spec/03-slice/03-slice.md +103 -0
  38. package/docs/specs/01-spec/03-slice/CLOSURE_BRIEF.md +54 -0
  39. package/docs/specs/01-spec/03-slice/EXECUTION_BRIEF.md +39 -0
  40. package/docs/specs/01-spec/04-slice/04-slice.json +36 -0
  41. package/docs/specs/01-spec/04-slice/04-slice.md +107 -0
  42. package/docs/specs/01-spec/04-slice/CLOSURE_BRIEF.md +46 -0
  43. package/docs/specs/01-spec/04-slice/EXECUTION_BRIEF.md +39 -0
  44. package/docs/specs/01-spec/05-slice/05-slice.json +36 -0
  45. package/docs/specs/01-spec/05-slice/05-slice.md +106 -0
  46. package/docs/specs/01-spec/05-slice/CLOSURE_BRIEF.md +84 -0
  47. package/docs/specs/01-spec/05-slice/EXECUTION_BRIEF.md +109 -0
  48. package/docs/specs/01-spec/06-slice/06-slice.json +36 -0
  49. package/docs/specs/01-spec/06-slice/06-slice.md +107 -0
  50. package/docs/specs/01-spec/06-slice/CLOSURE_BRIEF.md +67 -0
  51. package/docs/specs/01-spec/06-slice/EXECUTION_BRIEF.md +39 -0
  52. package/docs/specs/01-spec/07-slice/07-slice.json +36 -0
  53. package/docs/specs/01-spec/07-slice/07-slice.md +105 -0
  54. package/docs/specs/01-spec/07-slice/CLOSURE_BRIEF.md +71 -0
  55. package/docs/specs/01-spec/07-slice/EXECUTION_BRIEF.md +39 -0
  56. package/docs/specs/01-spec/08-slice/08-slice.json +36 -0
  57. package/docs/specs/01-spec/08-slice/08-slice.md +105 -0
  58. package/docs/specs/01-spec/08-slice/CLOSURE_BRIEF.md +114 -0
  59. package/docs/specs/01-spec/08-slice/EXECUTION_BRIEF.md +39 -0
  60. package/docs/specs/01-spec/CLOSURE_BRIEF.md +87 -0
  61. package/docs/specs/01-spec/spec.md +197 -0
  62. package/docs/workflows/full-ai-spec-to-pr.md +46 -3
  63. package/i18n/es/docs/GITFLOW_PR_GUIDE.md.template +38 -1
  64. package/package.json +27 -1
  65. package/scripts/package-quiver.sh +82 -2
  66. package/scripts/release-quiver.sh +14 -3
  67. package/specs/[project-name]/slices/pr.md.template +19 -0
  68. package/specs/quiver-v43-cli-i18n-audit-release-readiness/command-language-mode-matrix.json +122 -1
  69. package/specs/quiver-v46-deep-project-analysis/EVIDENCE_REPORT.md +94 -0
  70. package/specs/quiver-v46-deep-project-analysis/EXECUTION_PLAN.md +26 -0
  71. package/specs/quiver-v46-deep-project-analysis/SPEC.md +157 -0
  72. package/specs/quiver-v46-deep-project-analysis/STATUS.md +26 -0
  73. package/specs/quiver-v46-deep-project-analysis/pr.md +131 -0
  74. package/specs/quiver-v46-deep-project-analysis/slices/slice-00-analysis-contract-foundation/CLOSURE_BRIEF.md +14 -0
  75. package/specs/quiver-v46-deep-project-analysis/slices/slice-00-analysis-contract-foundation/EXECUTION_BRIEF.md +41 -0
  76. package/specs/quiver-v46-deep-project-analysis/slices/slice-00-analysis-contract-foundation/slice.json +61 -0
  77. package/specs/quiver-v46-deep-project-analysis/slices/slice-01-stack-agnostic-discovery-sampling/CLOSURE_BRIEF.md +22 -0
  78. package/specs/quiver-v46-deep-project-analysis/slices/slice-01-stack-agnostic-discovery-sampling/EXECUTION_BRIEF.md +33 -0
  79. package/specs/quiver-v46-deep-project-analysis/slices/slice-01-stack-agnostic-discovery-sampling/slice.json +80 -0
  80. package/specs/quiver-v46-deep-project-analysis/slices/slice-02-provider-analysis-json-contract/CLOSURE_BRIEF.md +21 -0
  81. package/specs/quiver-v46-deep-project-analysis/slices/slice-02-provider-analysis-json-contract/EXECUTION_BRIEF.md +34 -0
  82. package/specs/quiver-v46-deep-project-analysis/slices/slice-02-provider-analysis-json-contract/slice.json +79 -0
  83. package/specs/quiver-v46-deep-project-analysis/slices/slice-03-doc-proposal-review-safe-writes/CLOSURE_BRIEF.md +19 -0
  84. package/specs/quiver-v46-deep-project-analysis/slices/slice-03-doc-proposal-review-safe-writes/EXECUTION_BRIEF.md +33 -0
  85. package/specs/quiver-v46-deep-project-analysis/slices/slice-03-doc-proposal-review-safe-writes/slice.json +79 -0
  86. package/specs/quiver-v46-deep-project-analysis/slices/slice-04-validation-docs-release-readiness/CLOSURE_BRIEF.md +20 -0
  87. package/specs/quiver-v46-deep-project-analysis/slices/slice-04-validation-docs-release-readiness/EXECUTION_BRIEF.md +32 -0
  88. package/specs/quiver-v46-deep-project-analysis/slices/slice-04-validation-docs-release-readiness/slice.json +85 -0
  89. package/specs/quiver-v50-audit-trust-foundation/EVIDENCE_REPORT.md +151 -0
  90. package/specs/quiver-v50-audit-trust-foundation/EXECUTION_PLAN.md +34 -0
  91. package/specs/quiver-v50-audit-trust-foundation/SPEC.md +113 -0
  92. package/specs/quiver-v50-audit-trust-foundation/STATUS.md +26 -0
  93. package/specs/quiver-v50-audit-trust-foundation/pr.md +120 -0
  94. package/specs/quiver-v50-audit-trust-foundation/slices/slice-00-audit-baseline-and-resolved-findings/CLOSURE_BRIEF.md +20 -0
  95. package/specs/quiver-v50-audit-trust-foundation/slices/slice-00-audit-baseline-and-resolved-findings/EXECUTION_BRIEF.md +58 -0
  96. package/specs/quiver-v50-audit-trust-foundation/slices/slice-00-audit-baseline-and-resolved-findings/slice.json +57 -0
  97. package/specs/quiver-v50-audit-trust-foundation/slices/slice-01-runtime-minimum-and-package-metadata/CLOSURE_BRIEF.md +24 -0
  98. package/specs/quiver-v50-audit-trust-foundation/slices/slice-01-runtime-minimum-and-package-metadata/EXECUTION_BRIEF.md +67 -0
  99. package/specs/quiver-v50-audit-trust-foundation/slices/slice-01-runtime-minimum-and-package-metadata/slice.json +71 -0
  100. package/specs/quiver-v50-audit-trust-foundation/slices/slice-02-migrate-write-safety-contract/CLOSURE_BRIEF.md +24 -0
  101. package/specs/quiver-v50-audit-trust-foundation/slices/slice-02-migrate-write-safety-contract/EXECUTION_BRIEF.md +71 -0
  102. package/specs/quiver-v50-audit-trust-foundation/slices/slice-02-migrate-write-safety-contract/slice.json +81 -0
  103. package/specs/quiver-v50-audit-trust-foundation/slices/slice-03-security-reporting-channel/CLOSURE_BRIEF.md +22 -0
  104. package/specs/quiver-v50-audit-trust-foundation/slices/slice-03-security-reporting-channel/EXECUTION_BRIEF.md +57 -0
  105. package/specs/quiver-v50-audit-trust-foundation/slices/slice-03-security-reporting-channel/slice.json +56 -0
  106. package/specs/quiver-v50-audit-trust-foundation/slices/slice-04-user-facing-i18n-error-coverage/CLOSURE_BRIEF.md +23 -0
  107. package/specs/quiver-v50-audit-trust-foundation/slices/slice-04-user-facing-i18n-error-coverage/EXECUTION_BRIEF.md +68 -0
  108. package/specs/quiver-v50-audit-trust-foundation/slices/slice-04-user-facing-i18n-error-coverage/slice.json +84 -0
  109. package/specs/quiver-v50-audit-trust-foundation/slices/slice-05-init-analyze-progress-and-summaries/CLOSURE_BRIEF.md +25 -0
  110. package/specs/quiver-v50-audit-trust-foundation/slices/slice-05-init-analyze-progress-and-summaries/EXECUTION_BRIEF.md +73 -0
  111. package/specs/quiver-v50-audit-trust-foundation/slices/slice-05-init-analyze-progress-and-summaries/slice.json +72 -0
  112. package/specs/quiver-v50-audit-trust-foundation/slices/slice-06-contributor-and-architecture-docs/CLOSURE_BRIEF.md +22 -0
  113. package/specs/quiver-v50-audit-trust-foundation/slices/slice-06-contributor-and-architecture-docs/EXECUTION_BRIEF.md +66 -0
  114. package/specs/quiver-v50-audit-trust-foundation/slices/slice-06-contributor-and-architecture-docs/slice.json +74 -0
  115. package/specs/quiver-v50-audit-trust-foundation/slices/slice-07-ci-and-documentation-lint-baseline/CLOSURE_BRIEF.md +29 -0
  116. package/specs/quiver-v50-audit-trust-foundation/slices/slice-07-ci-and-documentation-lint-baseline/EXECUTION_BRIEF.md +74 -0
  117. package/specs/quiver-v50-audit-trust-foundation/slices/slice-07-ci-and-documentation-lint-baseline/slice.json +81 -0
  118. package/specs/quiver-v51-cli-ergonomics-automation-contracts/EVIDENCE_REPORT.md +145 -0
  119. package/specs/quiver-v51-cli-ergonomics-automation-contracts/EXECUTION_PLAN.md +32 -0
  120. package/specs/quiver-v51-cli-ergonomics-automation-contracts/SPEC.md +109 -0
  121. package/specs/quiver-v51-cli-ergonomics-automation-contracts/STATUS.md +25 -0
  122. package/specs/quiver-v51-cli-ergonomics-automation-contracts/pr.md +69 -0
  123. package/specs/quiver-v51-cli-ergonomics-automation-contracts/slices/slice-00-cli-contract-baseline/CLOSURE_BRIEF.md +20 -0
  124. package/specs/quiver-v51-cli-ergonomics-automation-contracts/slices/slice-00-cli-contract-baseline/EXECUTION_BRIEF.md +58 -0
  125. package/specs/quiver-v51-cli-ergonomics-automation-contracts/slices/slice-00-cli-contract-baseline/slice.json +56 -0
  126. package/specs/quiver-v51-cli-ergonomics-automation-contracts/slices/slice-01-flow-json-compatibility/CLOSURE_BRIEF.md +21 -0
  127. package/specs/quiver-v51-cli-ergonomics-automation-contracts/slices/slice-01-flow-json-compatibility/EXECUTION_BRIEF.md +61 -0
  128. package/specs/quiver-v51-cli-ergonomics-automation-contracts/slices/slice-01-flow-json-compatibility/slice.json +68 -0
  129. package/specs/quiver-v51-cli-ergonomics-automation-contracts/slices/slice-02-dashboard-section-validation-i18n/CLOSURE_BRIEF.md +25 -0
  130. package/specs/quiver-v51-cli-ergonomics-automation-contracts/slices/slice-02-dashboard-section-validation-i18n/EXECUTION_BRIEF.md +65 -0
  131. package/specs/quiver-v51-cli-ergonomics-automation-contracts/slices/slice-02-dashboard-section-validation-i18n/pr.md +134 -0
  132. package/specs/quiver-v51-cli-ergonomics-automation-contracts/slices/slice-02-dashboard-section-validation-i18n/slice.json +80 -0
  133. package/specs/quiver-v51-cli-ergonomics-automation-contracts/slices/slice-03-base-branch-resolution-policy/CLOSURE_BRIEF.md +31 -0
  134. package/specs/quiver-v51-cli-ergonomics-automation-contracts/slices/slice-03-base-branch-resolution-policy/EXECUTION_BRIEF.md +80 -0
  135. package/specs/quiver-v51-cli-ergonomics-automation-contracts/slices/slice-03-base-branch-resolution-policy/pr.md +159 -0
  136. package/specs/quiver-v51-cli-ergonomics-automation-contracts/slices/slice-03-base-branch-resolution-policy/slice.json +102 -0
  137. package/specs/quiver-v51-cli-ergonomics-automation-contracts/slices/slice-04-next-plan-graph-ux-edge-cases/CLOSURE_BRIEF.md +25 -0
  138. package/specs/quiver-v51-cli-ergonomics-automation-contracts/slices/slice-04-next-plan-graph-ux-edge-cases/EXECUTION_BRIEF.md +74 -0
  139. package/specs/quiver-v51-cli-ergonomics-automation-contracts/slices/slice-04-next-plan-graph-ux-edge-cases/pr.md +147 -0
  140. package/specs/quiver-v51-cli-ergonomics-automation-contracts/slices/slice-04-next-plan-graph-ux-edge-cases/slice.json +91 -0
  141. package/specs/quiver-v51-cli-ergonomics-automation-contracts/slices/slice-05-evidence-robustness-path-safety/CLOSURE_BRIEF.md +28 -0
  142. package/specs/quiver-v51-cli-ergonomics-automation-contracts/slices/slice-05-evidence-robustness-path-safety/EXECUTION_BRIEF.md +73 -0
  143. package/specs/quiver-v51-cli-ergonomics-automation-contracts/slices/slice-05-evidence-robustness-path-safety/pr.md +146 -0
  144. package/specs/quiver-v51-cli-ergonomics-automation-contracts/slices/slice-05-evidence-robustness-path-safety/slice.json +100 -0
  145. package/specs/quiver-v51-cli-ergonomics-automation-contracts/slices/slice-06-namespace-compatibility-windows-scripts/CLOSURE_BRIEF.md +36 -0
  146. package/specs/quiver-v51-cli-ergonomics-automation-contracts/slices/slice-06-namespace-compatibility-windows-scripts/EXECUTION_BRIEF.md +76 -0
  147. package/specs/quiver-v51-cli-ergonomics-automation-contracts/slices/slice-06-namespace-compatibility-windows-scripts/pr.md +155 -0
  148. package/specs/quiver-v51-cli-ergonomics-automation-contracts/slices/slice-06-namespace-compatibility-windows-scripts/slice.json +111 -0
  149. package/specs/quiver-v52-schema-docs-release-hygiene/EVIDENCE_REPORT.md +95 -0
  150. package/specs/quiver-v52-schema-docs-release-hygiene/EXECUTION_PLAN.md +31 -0
  151. package/specs/quiver-v52-schema-docs-release-hygiene/SPEC.md +107 -0
  152. package/specs/quiver-v52-schema-docs-release-hygiene/STATUS.md +22 -0
  153. package/specs/quiver-v52-schema-docs-release-hygiene/pr.md +69 -0
  154. package/specs/quiver-v52-schema-docs-release-hygiene/slices/slice-00-schema-docs-release-baseline/CLOSURE_BRIEF.md +21 -0
  155. package/specs/quiver-v52-schema-docs-release-hygiene/slices/slice-00-schema-docs-release-baseline/EXECUTION_BRIEF.md +60 -0
  156. package/specs/quiver-v52-schema-docs-release-hygiene/slices/slice-00-schema-docs-release-baseline/slice.json +62 -0
  157. package/specs/quiver-v52-schema-docs-release-hygiene/slices/slice-01-json-schema-for-slice-json/CLOSURE_BRIEF.md +34 -0
  158. package/specs/quiver-v52-schema-docs-release-hygiene/slices/slice-01-json-schema-for-slice-json/EXECUTION_BRIEF.md +71 -0
  159. package/specs/quiver-v52-schema-docs-release-hygiene/slices/slice-01-json-schema-for-slice-json/pr.md +146 -0
  160. package/specs/quiver-v52-schema-docs-release-hygiene/slices/slice-01-json-schema-for-slice-json/slice.json +106 -0
  161. package/specs/quiver-v52-schema-docs-release-hygiene/slices/slice-02-generated-cli-reference/CLOSURE_BRIEF.md +33 -0
  162. package/specs/quiver-v52-schema-docs-release-hygiene/slices/slice-02-generated-cli-reference/EXECUTION_BRIEF.md +67 -0
  163. package/specs/quiver-v52-schema-docs-release-hygiene/slices/slice-02-generated-cli-reference/pr.md +144 -0
  164. package/specs/quiver-v52-schema-docs-release-hygiene/slices/slice-02-generated-cli-reference/slice.json +86 -0
  165. package/specs/quiver-v52-schema-docs-release-hygiene/slices/slice-03-changelog-package-release-smoke-hygiene/CLOSURE_BRIEF.md +38 -0
  166. package/specs/quiver-v52-schema-docs-release-hygiene/slices/slice-03-changelog-package-release-smoke-hygiene/EXECUTION_BRIEF.md +75 -0
  167. package/specs/quiver-v52-schema-docs-release-hygiene/slices/slice-03-changelog-package-release-smoke-hygiene/pr.md +159 -0
  168. package/specs/quiver-v52-schema-docs-release-hygiene/slices/slice-03-changelog-package-release-smoke-hygiene/slice.json +105 -0
  169. package/src/create-quiver/commands/ai.js +521 -0
  170. package/src/create-quiver/commands/changelog.js +93 -0
  171. package/src/create-quiver/commands/config.js +16 -5
  172. package/src/create-quiver/commands/evidence.js +33 -2
  173. package/src/create-quiver/commands/flow.js +1 -0
  174. package/src/create-quiver/commands/graph.js +8 -2
  175. package/src/create-quiver/commands/plan.js +6 -0
  176. package/src/create-quiver/commands/spec.js +28 -13
  177. package/src/create-quiver/index.js +408 -98
  178. package/src/create-quiver/lib/ai/analyze-project-discovery.js +387 -0
  179. package/src/create-quiver/lib/ai/analyze-project-docs.js +364 -0
  180. package/src/create-quiver/lib/ai/analyze-project-parser.js +277 -0
  181. package/src/create-quiver/lib/ai/analyze-project-prompts.js +214 -0
  182. package/src/create-quiver/lib/ai/analyze-project-review.js +99 -0
  183. package/src/create-quiver/lib/ai/analyze-project-sampling.js +402 -0
  184. package/src/create-quiver/lib/ai/analyze-project-schema.js +92 -0
  185. package/src/create-quiver/lib/ai/analyze-project-validation.js +309 -0
  186. package/src/create-quiver/lib/ai/artifacts.js +4 -1
  187. package/src/create-quiver/lib/ai/github.js +5 -2
  188. package/src/create-quiver/lib/ai/spec-templates.js +19 -0
  189. package/src/create-quiver/lib/cli/command-registry.js +72 -0
  190. package/src/create-quiver/lib/cli/parser.js +14 -0
  191. package/src/create-quiver/lib/evidence.js +161 -6
  192. package/src/create-quiver/lib/git.js +99 -0
  193. package/src/create-quiver/lib/i18n/messages/en.js +52 -7
  194. package/src/create-quiver/lib/i18n/messages/es.js +54 -9
  195. package/src/create-quiver/lib/init-docs.js +1 -1
  196. package/src/create-quiver/lib/init-layout.js +9 -8
  197. package/src/create-quiver/lib/lifecycle.js +24 -26
  198. package/src/create-quiver/lib/readiness.js +67 -56
  199. package/src/create-quiver/lib/spec-worktrees.js +16 -34
@@ -0,0 +1,364 @@
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 {
8
+ ALLOWED_ANALYZE_DOC_UPDATE_PATHS,
9
+ MAX_ANALYZE_DOC_UPDATE_LENGTH,
10
+ } = require('./analyze-project-schema');
11
+ const { redactSensitiveLocalValues } = require('./artifacts');
12
+ const { validateProjectRelativePath } = require('../paths');
13
+
14
+ const ANALYZE_DOC_PROPOSAL_SCHEMA_VERSION = 1;
15
+ const ANALYZE_DOC_PROPOSAL_KIND = 'quiver-analyze-project-doc-proposal';
16
+ const ANALYZE_MANAGED_START = '<!-- quiver:analyze-project:start -->';
17
+ const ANALYZE_MANAGED_END = '<!-- quiver:analyze-project:end -->';
18
+
19
+ const docProposalEntrySchema = z.object({
20
+ path: z.string().trim().min(1),
21
+ action: z.enum(['create', 'update', 'skip']).default('update'),
22
+ content: z.string().max(MAX_ANALYZE_DOC_UPDATE_LENGTH).default(''),
23
+ reason: z.string().trim().max(2_000).default('AI analyze-project proposed this update.'),
24
+ }).strict();
25
+
26
+ const analyzeProjectDocProposalSchema = z.object({
27
+ schema_version: z.literal(ANALYZE_DOC_PROPOSAL_SCHEMA_VERSION).default(ANALYZE_DOC_PROPOSAL_SCHEMA_VERSION),
28
+ kind: z.literal(ANALYZE_DOC_PROPOSAL_KIND).default(ANALYZE_DOC_PROPOSAL_KIND),
29
+ summary: z.string().trim().max(4_000).default(''),
30
+ docs: z.array(docProposalEntrySchema).default([]),
31
+ }).strict();
32
+
33
+ function formatError(message) {
34
+ return `create-quiver: ${message}`;
35
+ }
36
+
37
+ class AnalyzeProjectDocsError extends Error {
38
+ constructor(message, issues = []) {
39
+ super(formatError(message));
40
+ this.name = 'AnalyzeProjectDocsError';
41
+ this.code = 'AI_ANALYZE_PROJECT_DOCS_INVALID';
42
+ this.issues = issues;
43
+ }
44
+ }
45
+
46
+ function sha256(contents) {
47
+ return crypto.createHash('sha256').update(String(contents || ''), 'utf8').digest('hex');
48
+ }
49
+
50
+ function buildDiffSnippet(filePath, currentContent, proposedContent) {
51
+ const currentLines = String(currentContent || '').split('\n').slice(0, 6);
52
+ const proposedLines = String(proposedContent || '').split('\n').slice(0, 6);
53
+ const lines = [
54
+ `--- ${filePath} (current)`,
55
+ `+++ ${filePath} (proposed)`,
56
+ ];
57
+ for (const line of currentLines) {
58
+ if (line) lines.push(`- ${line}`);
59
+ }
60
+ for (const line of proposedLines) {
61
+ if (line) lines.push(`+ ${line}`);
62
+ }
63
+ return lines;
64
+ }
65
+
66
+ function normalizeDocContent(content) {
67
+ return `${String(content || '').replace(/\s+$/g, '')}\n`;
68
+ }
69
+
70
+ function managedBlock(content) {
71
+ return [
72
+ ANALYZE_MANAGED_START,
73
+ normalizeDocContent(content).trimEnd(),
74
+ ANALYZE_MANAGED_END,
75
+ '',
76
+ ].join('\n');
77
+ }
78
+
79
+ function mergeManagedBlock(currentContent, proposedContent) {
80
+ const current = String(currentContent || '');
81
+ const block = managedBlock(proposedContent);
82
+ const startIndex = current.indexOf(ANALYZE_MANAGED_START);
83
+ const endIndex = current.indexOf(ANALYZE_MANAGED_END);
84
+
85
+ if (startIndex >= 0 && endIndex > startIndex) {
86
+ return `${current.slice(0, startIndex)}${block}${current.slice(endIndex + ANALYZE_MANAGED_END.length).replace(/^\s*\n?/, '')}`;
87
+ }
88
+
89
+ if (!current.trim()) {
90
+ return block;
91
+ }
92
+
93
+ return `${current.replace(/\s+$/g, '')}\n\n${block}`;
94
+ }
95
+
96
+ function validateDocPath(docPath) {
97
+ let normalized;
98
+ try {
99
+ normalized = validateProjectRelativePath(docPath, 'analyze-project doc update path');
100
+ } catch (error) {
101
+ return {
102
+ ok: false,
103
+ path: String(docPath || ''),
104
+ issue: 'invalid-project-relative-path',
105
+ message: error.message,
106
+ };
107
+ }
108
+
109
+ if (!ALLOWED_ANALYZE_DOC_UPDATE_PATHS.includes(normalized)) {
110
+ return {
111
+ ok: false,
112
+ path: normalized,
113
+ issue: 'unapproved-doc-update-path',
114
+ message: `analyze-project can only update approved Markdown docs: ${ALLOWED_ANALYZE_DOC_UPDATE_PATHS.join(', ')}`,
115
+ };
116
+ }
117
+
118
+ if (!normalized.endsWith('.md')) {
119
+ return {
120
+ ok: false,
121
+ path: normalized,
122
+ issue: 'not-markdown',
123
+ message: 'analyze-project doc updates must target Markdown files.',
124
+ };
125
+ }
126
+
127
+ return { ok: true, path: normalized };
128
+ }
129
+
130
+ function mapZodIssues(error) {
131
+ return error.issues.map((issue) => ({
132
+ path: issue.path.length > 0 ? issue.path.join('.') : null,
133
+ issue: issue.code,
134
+ message: issue.message,
135
+ }));
136
+ }
137
+
138
+ function normalizeAnalyzeProjectDocProposal(value) {
139
+ const parsed = analyzeProjectDocProposalSchema.safeParse(value);
140
+ if (!parsed.success) {
141
+ throw new AnalyzeProjectDocsError('analyze-project doc proposal does not match the required schema', mapZodIssues(parsed.error));
142
+ }
143
+
144
+ const issues = [];
145
+ const seen = new Set();
146
+ const docs = [];
147
+
148
+ for (const [index, doc] of parsed.data.docs.entries()) {
149
+ const pathResult = validateDocPath(doc.path);
150
+ if (!pathResult.ok) {
151
+ issues.push({ index, ...pathResult });
152
+ continue;
153
+ }
154
+ if (seen.has(pathResult.path)) {
155
+ issues.push({
156
+ index,
157
+ path: pathResult.path,
158
+ issue: 'duplicate-path',
159
+ message: 'proposal contains multiple updates for the same doc path',
160
+ });
161
+ continue;
162
+ }
163
+ seen.add(pathResult.path);
164
+ if (doc.action !== 'skip' && !doc.content.trim()) {
165
+ issues.push({
166
+ index,
167
+ path: pathResult.path,
168
+ issue: 'missing-content',
169
+ message: 'create/update doc proposals require non-empty Markdown content',
170
+ });
171
+ continue;
172
+ }
173
+ docs.push({
174
+ ...doc,
175
+ path: pathResult.path,
176
+ });
177
+ }
178
+
179
+ if (issues.length > 0) {
180
+ throw new AnalyzeProjectDocsError('analyze-project doc proposal contains unsafe or ambiguous writes', issues);
181
+ }
182
+
183
+ return {
184
+ schema_version: parsed.data.schema_version,
185
+ kind: parsed.data.kind,
186
+ summary: parsed.data.summary,
187
+ docs,
188
+ };
189
+ }
190
+
191
+ function parseAnalyzeProjectDocProposal(text) {
192
+ let parsed;
193
+ try {
194
+ parsed = JSON.parse(String(text || ''));
195
+ } catch (error) {
196
+ throw new AnalyzeProjectDocsError('edited analyze-project doc proposal is not valid JSON', [
197
+ { path: null, issue: 'malformed-json', message: error.message },
198
+ ]);
199
+ }
200
+ return normalizeAnalyzeProjectDocProposal(parsed);
201
+ }
202
+
203
+ function fallbackDocUpdatesFromAnalysis(analysis) {
204
+ const productName = analysis?.product?.name?.name || 'Unknown product';
205
+ const features = (analysis?.features || []).map((feature) => `- ${feature.name} (${feature.confidence})`).join('\n') || '- Unknown';
206
+ return {
207
+ 'docs/CONTEXTO.md': `# Context\n\nProduct: ${productName}\n\n## Features\n${features}\n`,
208
+ 'docs/AI_CONTEXT.md': `# AI Context\n\nProduct: ${productName}\n\nUse this context as inferred evidence, not as unverified fact.\n`,
209
+ 'docs/ARCHITECTURE.md': '# Architecture\n\nArchitecture details were not fully confirmed by the current analysis.\n',
210
+ };
211
+ }
212
+
213
+ function buildAnalyzeProjectDocProposal(analysis, options = {}) {
214
+ const docUpdates = analysis?.doc_updates && Object.keys(analysis.doc_updates).length > 0
215
+ ? analysis.doc_updates
216
+ : fallbackDocUpdatesFromAnalysis(analysis);
217
+ const docs = Object.keys(docUpdates)
218
+ .sort()
219
+ .map((docPath) => ({
220
+ path: docPath,
221
+ action: 'update',
222
+ content: docUpdates[docPath],
223
+ reason: docPath === 'docs/PROJECT_MAP.md'
224
+ ? 'AI content constrained to the Quiver analyze-project managed block.'
225
+ : 'AI analyze-project proposed a managed documentation update.',
226
+ }));
227
+
228
+ return normalizeAnalyzeProjectDocProposal({
229
+ schema_version: ANALYZE_DOC_PROPOSAL_SCHEMA_VERSION,
230
+ kind: ANALYZE_DOC_PROPOSAL_KIND,
231
+ summary: options.summary || 'Documentation updates proposed from validated analyze-project output.',
232
+ docs,
233
+ });
234
+ }
235
+
236
+ function buildAnalyzeProjectWritePlan(repoRoot, proposal) {
237
+ return proposal.docs.map((doc) => {
238
+ const destinationPath = path.join(repoRoot, doc.path);
239
+ const exists = fs.existsSync(destinationPath);
240
+ const currentContent = exists ? fs.readFileSync(destinationPath, 'utf8') : '';
241
+ const proposedContent = doc.action === 'skip'
242
+ ? currentContent
243
+ : mergeManagedBlock(currentContent, doc.content);
244
+ const changed = currentContent.replace(/\r\n/g, '\n') !== proposedContent.replace(/\r\n/g, '\n');
245
+ const action = doc.action === 'skip' || !changed ? 'skip' : exists ? 'update' : 'create';
246
+
247
+ return {
248
+ path: doc.path,
249
+ destinationPath,
250
+ action,
251
+ reason: action === 'skip' ? 'already up to date or skipped by proposal' : doc.reason,
252
+ exists,
253
+ dirty: exists && action === 'update' && currentContent.trim().length > 0,
254
+ currentContent,
255
+ proposedContent,
256
+ before_sha256: exists ? sha256(currentContent) : null,
257
+ after_sha256: action === 'skip' ? (exists ? sha256(currentContent) : null) : sha256(proposedContent),
258
+ diff: buildDiffSnippet(doc.path, currentContent, proposedContent),
259
+ };
260
+ });
261
+ }
262
+
263
+ function createAnalyzeProjectSnapshot(repoRoot, run, writePlan, options = {}) {
264
+ const now = options.now || new Date();
265
+ const stamp = now.toISOString().replace(/[-:]/g, '').replace(/\.\d{3}Z$/, 'Z');
266
+ const snapshotRoot = path.join(repoRoot, '.quiver', 'runs', run.run_id, 'snapshots', stamp);
267
+ const rawRoot = path.join(repoRoot, '.quiver', 'runs', run.run_id, 'raw');
268
+ const manifest = {
269
+ schema_version: 1,
270
+ kind: 'quiver-analyze-project-write-manifest',
271
+ run_id: run.run_id,
272
+ created_at: now.toISOString(),
273
+ entries: [],
274
+ restore_guidance: 'Copy a snapshot_path back to path to restore a previous file, or delete created files listed with existed=false.',
275
+ };
276
+
277
+ fs.mkdirSync(snapshotRoot, { recursive: true });
278
+ fs.mkdirSync(rawRoot, { recursive: true });
279
+
280
+ for (const item of writePlan) {
281
+ if (item.action === 'skip') {
282
+ continue;
283
+ }
284
+ const entry = {
285
+ path: item.path,
286
+ action: item.action,
287
+ existed: item.exists,
288
+ dirty: item.dirty,
289
+ before_sha256: item.before_sha256,
290
+ after_sha256: item.after_sha256,
291
+ snapshot_path: null,
292
+ restore_hint: item.exists ? `Copy snapshot_path back to ${item.path}.` : `Delete ${item.path} to undo this created file.`,
293
+ };
294
+
295
+ if (item.exists) {
296
+ const snapshotPath = path.join(snapshotRoot, item.path);
297
+ fs.mkdirSync(path.dirname(snapshotPath), { recursive: true });
298
+ fs.copyFileSync(item.destinationPath, snapshotPath);
299
+ entry.snapshot_path = path.relative(repoRoot, snapshotPath).split(path.sep).join('/');
300
+ }
301
+ manifest.entries.push(entry);
302
+ }
303
+
304
+ const providerArtifactPath = path.join(rawRoot, 'analyze-project-provider-artifact.json');
305
+ const providerArtifact = options.providerArtifact
306
+ ? JSON.parse(redactSensitiveLocalValues(JSON.stringify(options.providerArtifact), { projectRoot: repoRoot }))
307
+ : null;
308
+ fs.writeFileSync(providerArtifactPath, `${JSON.stringify(providerArtifact, null, 2)}\n`);
309
+
310
+ const proposalPath = path.join(rawRoot, 'analyze-project-doc-proposal.json');
311
+ fs.writeFileSync(proposalPath, `${JSON.stringify(options.proposal || null, null, 2)}\n`);
312
+
313
+ const manifestPath = path.join(snapshotRoot, 'manifest.json');
314
+ fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
315
+
316
+ return {
317
+ root: path.relative(repoRoot, snapshotRoot).split(path.sep).join('/'),
318
+ manifestPath: path.relative(repoRoot, manifestPath).split(path.sep).join('/'),
319
+ providerArtifactPath: path.relative(repoRoot, providerArtifactPath).split(path.sep).join('/'),
320
+ proposalPath: path.relative(repoRoot, proposalPath).split(path.sep).join('/'),
321
+ entries: manifest.entries,
322
+ };
323
+ }
324
+
325
+ function writeAnalyzeProjectDocs(writePlan) {
326
+ const writtenDocs = [];
327
+ for (const item of writePlan) {
328
+ if (item.action === 'skip') {
329
+ continue;
330
+ }
331
+ fs.mkdirSync(path.dirname(item.destinationPath), { recursive: true });
332
+ fs.writeFileSync(item.destinationPath, item.proposedContent);
333
+ writtenDocs.push(item.path);
334
+ }
335
+ return writtenDocs;
336
+ }
337
+
338
+ function formatAnalyzeProjectDiffPreview(writePlan) {
339
+ const lines = [];
340
+ for (const item of writePlan) {
341
+ if (item.action === 'skip') {
342
+ continue;
343
+ }
344
+ lines.push(...item.diff);
345
+ }
346
+ return lines.length > 0 ? lines : ['- no changes'];
347
+ }
348
+
349
+ module.exports = {
350
+ ANALYZE_DOC_PROPOSAL_KIND,
351
+ ANALYZE_DOC_PROPOSAL_SCHEMA_VERSION,
352
+ ANALYZE_MANAGED_END,
353
+ ANALYZE_MANAGED_START,
354
+ AnalyzeProjectDocsError,
355
+ analyzeProjectDocProposalSchema,
356
+ buildAnalyzeProjectDocProposal,
357
+ buildAnalyzeProjectWritePlan,
358
+ createAnalyzeProjectSnapshot,
359
+ formatAnalyzeProjectDiffPreview,
360
+ mergeManagedBlock,
361
+ normalizeAnalyzeProjectDocProposal,
362
+ parseAnalyzeProjectDocProposal,
363
+ writeAnalyzeProjectDocs,
364
+ };
@@ -0,0 +1,277 @@
1
+ const {
2
+ ALLOWED_ANALYZE_DOC_UPDATE_PATHS,
3
+ analyzeProjectSchema,
4
+ } = require('./analyze-project-schema');
5
+ const { extractBalancedJsonObject } = require('./context-proposal');
6
+
7
+ function formatError(message) {
8
+ return `create-quiver: ${message}`;
9
+ }
10
+
11
+ class AnalyzeProjectAnalysisError extends Error {
12
+ constructor(message, issues = []) {
13
+ super(formatError(message));
14
+ this.name = 'AnalyzeProjectAnalysisError';
15
+ this.code = 'AI_ANALYZE_PROJECT_INVALID';
16
+ this.issues = issues;
17
+ }
18
+ }
19
+
20
+ function normalizeAnalysisShape(value) {
21
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
22
+ return value;
23
+ }
24
+ const normalized = { ...value };
25
+ if (normalized.schemaVersion && !normalized.schema_version) {
26
+ normalized.schema_version = normalized.schemaVersion;
27
+ delete normalized.schemaVersion;
28
+ }
29
+ if (normalized.docUpdates && !normalized.doc_updates) {
30
+ normalized.doc_updates = normalized.docUpdates;
31
+ delete normalized.docUpdates;
32
+ }
33
+ return normalized;
34
+ }
35
+
36
+ function parseJsonCandidate(jsonText, source) {
37
+ try {
38
+ return {
39
+ ok: true,
40
+ source,
41
+ value: JSON.parse(jsonText),
42
+ };
43
+ } catch (error) {
44
+ return {
45
+ ok: false,
46
+ source,
47
+ error,
48
+ };
49
+ }
50
+ }
51
+
52
+ function parseProviderAnalysisJson(text) {
53
+ const input = String(text || '').trim();
54
+ if (!input) {
55
+ throw new AnalyzeProjectAnalysisError('provider analysis output is empty', [
56
+ { path: null, issue: 'empty-output', message: 'The provider returned no analysis JSON.' },
57
+ ]);
58
+ }
59
+
60
+ const direct = parseJsonCandidate(input, 'raw-json');
61
+ if (direct.ok) {
62
+ return direct;
63
+ }
64
+
65
+ const fencedMatches = Array.from(input.matchAll(/```(?:json)?\s*([\s\S]*?)```/gi));
66
+ const parseFailures = [direct];
67
+ for (const match of fencedMatches) {
68
+ const parsed = parseJsonCandidate(match[1].trim(), 'fenced-json');
69
+ if (parsed.ok) {
70
+ return parsed;
71
+ }
72
+ parseFailures.push(parsed);
73
+ }
74
+
75
+ const balanced = extractBalancedJsonObject(input);
76
+ if (balanced) {
77
+ const parsed = parseJsonCandidate(balanced, 'embedded-json');
78
+ if (parsed.ok) {
79
+ return parsed;
80
+ }
81
+ parseFailures.push(parsed);
82
+ }
83
+
84
+ const firstError = parseFailures.find((item) => item.error)?.error;
85
+ throw new AnalyzeProjectAnalysisError('provider analysis output is not valid JSON', [
86
+ {
87
+ path: null,
88
+ issue: 'malformed-json',
89
+ message: firstError ? firstError.message : 'No JSON object or fenced JSON block could be parsed.',
90
+ },
91
+ ]);
92
+ }
93
+
94
+ function mapZodIssues(error) {
95
+ return error.issues.map((issue) => ({
96
+ path: issue.path.length > 0 ? issue.path.join('.') : null,
97
+ issue: issue.code,
98
+ message: issue.message,
99
+ }));
100
+ }
101
+
102
+ function normalizeSelectedFileMap(options = {}) {
103
+ const selectedFiles = Array.isArray(options.selectedFiles) ? options.selectedFiles : [];
104
+ const promptFiles = Array.isArray(options.promptFiles) ? options.promptFiles : [];
105
+ const fileMap = new Map();
106
+
107
+ for (const file of selectedFiles) {
108
+ fileMap.set(String(file.path || '').replace(/\\/g, '/'), {
109
+ selected: true,
110
+ truncated: false,
111
+ });
112
+ }
113
+ for (const file of promptFiles) {
114
+ const normalized = String(file.path || '').replace(/\\/g, '/');
115
+ const current = fileMap.get(normalized) || { selected: false, truncated: false };
116
+ fileMap.set(normalized, {
117
+ ...current,
118
+ truncated: file.truncated === true,
119
+ });
120
+ }
121
+
122
+ return fileMap;
123
+ }
124
+
125
+ function validateEvidenceList({ evidence, confidence, location, target, fileMap, issues, warnings }) {
126
+ const evidenceList = Array.isArray(evidence) ? evidence : [];
127
+ const requiresEvidence = confidence && confidence !== 'unknown';
128
+
129
+ if (requiresEvidence && evidenceList.length === 0) {
130
+ issues.push({
131
+ path: location,
132
+ issue: 'missing-evidence',
133
+ message: `${location} has confidence=${confidence} but no evidence paths.`,
134
+ });
135
+ return;
136
+ }
137
+
138
+ for (const evidencePath of evidenceList) {
139
+ const normalized = String(evidencePath || '').replace(/\\/g, '/');
140
+ const evidenceInfo = fileMap.get(normalized);
141
+ if (!evidenceInfo || evidenceInfo.selected !== true) {
142
+ issues.push({
143
+ path: location,
144
+ issue: 'evidence-not-selected',
145
+ message: `evidence path is not in the selected sample: ${normalized}`,
146
+ });
147
+ continue;
148
+ }
149
+
150
+ if (confidence === 'confirmed' && evidenceInfo.truncated) {
151
+ target.confidence = 'inferred';
152
+ warnings.push({
153
+ path: location,
154
+ issue: 'confirmed-downgraded-truncated-evidence',
155
+ message: `confirmed confidence downgraded to inferred because evidence file is truncated: ${normalized}`,
156
+ });
157
+ }
158
+ }
159
+ }
160
+
161
+ function validateClaim(claim, location, context) {
162
+ validateEvidenceList({
163
+ evidence: claim.evidence,
164
+ confidence: claim.confidence,
165
+ location,
166
+ target: claim,
167
+ ...context,
168
+ });
169
+ }
170
+
171
+ function validateFinding(finding, location, context) {
172
+ if (!finding) {
173
+ return;
174
+ }
175
+ validateEvidenceList({
176
+ evidence: finding.evidence,
177
+ confidence: finding.confidence,
178
+ location,
179
+ target: finding,
180
+ ...context,
181
+ });
182
+ }
183
+
184
+ function validateFindingArray(findings, location, context) {
185
+ for (const [index, finding] of (Array.isArray(findings) ? findings : []).entries()) {
186
+ validateFinding(finding, `${location}.${index}`, context);
187
+ }
188
+ }
189
+
190
+ function validateClaimArray(claims, location, context) {
191
+ for (const [index, claim] of (Array.isArray(claims) ? claims : []).entries()) {
192
+ validateClaim(claim, `${location}.${index}`, context);
193
+ }
194
+ }
195
+
196
+ function validateAnalysisEvidence(analysis, options = {}) {
197
+ const fileMap = normalizeSelectedFileMap(options);
198
+ const issues = [];
199
+ const warnings = [];
200
+ const context = { fileMap, issues, warnings };
201
+
202
+ validateFinding(analysis.product.name, 'product.name', context);
203
+ validateFinding(analysis.product.type, 'product.type', context);
204
+ validateClaimArray(analysis.product.claims, 'product.claims', context);
205
+
206
+ for (const key of ['roles', 'entities', 'actions', 'flows', 'incomplete_or_suspicious']) {
207
+ validateFindingArray(analysis.domain[key], `domain.${key}`, context);
208
+ }
209
+ validateClaimArray(analysis.domain.claims, 'domain.claims', context);
210
+
211
+ for (const key of ['frontend', 'backend', 'auth', 'persistence', 'integrations', 'state', 'api', 'testing', 'deploy', 'risks']) {
212
+ validateFindingArray(analysis.architecture[key], `architecture.${key}`, context);
213
+ }
214
+ validateClaimArray(analysis.architecture.claims, 'architecture.claims', context);
215
+ validateFindingArray(analysis.features, 'features', context);
216
+ validateFindingArray(analysis.risks, 'risks', context);
217
+ validateClaimArray(analysis.claims, 'claims', context);
218
+
219
+ for (const [index, question] of analysis.questions.entries()) {
220
+ validateEvidenceList({
221
+ evidence: question.evidence,
222
+ confidence: 'unknown',
223
+ location: `questions.${index}`,
224
+ target: question,
225
+ ...context,
226
+ });
227
+ }
228
+
229
+ for (const docPath of Object.keys(analysis.doc_updates || {})) {
230
+ if (!ALLOWED_ANALYZE_DOC_UPDATE_PATHS.includes(docPath)) {
231
+ issues.push({
232
+ path: `doc_updates.${docPath}`,
233
+ issue: 'unapproved-doc-update-path',
234
+ message: `doc_updates path is not approved for analyze-project: ${docPath}`,
235
+ });
236
+ }
237
+ }
238
+
239
+ if (issues.length > 0) {
240
+ throw new AnalyzeProjectAnalysisError('provider analysis JSON failed evidence validation', issues);
241
+ }
242
+
243
+ return {
244
+ warnings,
245
+ docUpdatePaths: Object.keys(analysis.doc_updates || {}),
246
+ };
247
+ }
248
+
249
+ function normalizeAnalyzeProjectAnalysis(value, options = {}) {
250
+ const parsed = analyzeProjectSchema.safeParse(normalizeAnalysisShape(value));
251
+ if (!parsed.success) {
252
+ throw new AnalyzeProjectAnalysisError('provider analysis JSON does not match the required schema', mapZodIssues(parsed.error));
253
+ }
254
+ const validation = validateAnalysisEvidence(parsed.data, options);
255
+ return {
256
+ analysis: parsed.data,
257
+ warnings: validation.warnings,
258
+ docUpdatePaths: validation.docUpdatePaths,
259
+ };
260
+ }
261
+
262
+ function parseAnalyzeProjectOutput(text, options = {}) {
263
+ const parsed = parseProviderAnalysisJson(text);
264
+ const normalized = normalizeAnalyzeProjectAnalysis(parsed.value, options);
265
+ return {
266
+ ...normalized,
267
+ parseSource: parsed.source,
268
+ };
269
+ }
270
+
271
+ module.exports = {
272
+ AnalyzeProjectAnalysisError,
273
+ normalizeAnalyzeProjectAnalysis,
274
+ parseAnalyzeProjectOutput,
275
+ parseProviderAnalysisJson,
276
+ validateAnalysisEvidence,
277
+ };