autonomous-sdlc-harness 0.1.0 → 0.2.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 (86) hide show
  1. package/README.md +4 -3
  2. package/dist/commands/docs.js +219 -0
  3. package/dist/commands/docs.js.map +1 -0
  4. package/dist/commands/doctor.js +5 -5
  5. package/dist/commands/doctor.js.map +1 -1
  6. package/dist/commands/init.js +138 -44
  7. package/dist/commands/init.js.map +1 -1
  8. package/dist/commands/registry.js +2 -0
  9. package/dist/commands/registry.js.map +1 -1
  10. package/dist/config/check.js +25 -5
  11. package/dist/config/check.js.map +1 -1
  12. package/dist/config/model.js +18 -1
  13. package/dist/config/model.js.map +1 -1
  14. package/dist/core/layerGapRemedy.js +3 -2
  15. package/dist/core/layerGapRemedy.js.map +1 -1
  16. package/dist/core/pluginIdentity.js +33 -0
  17. package/dist/core/pluginIdentity.js.map +1 -0
  18. package/dist/core/report.js +9 -0
  19. package/dist/core/report.js.map +1 -1
  20. package/dist/core/writer.js +22 -5
  21. package/dist/core/writer.js.map +1 -1
  22. package/dist/detect/presets.js +35 -26
  23. package/dist/detect/presets.js.map +1 -1
  24. package/dist/detect/signals.js +13 -9
  25. package/dist/detect/signals.js.map +1 -1
  26. package/dist/doctor/checks.js +141 -11
  27. package/dist/doctor/checks.js.map +1 -1
  28. package/dist/generators/claudeContext.js +4 -5
  29. package/dist/generators/claudeContext.js.map +1 -1
  30. package/dist/generators/harnessConfig.js +13 -5
  31. package/dist/generators/harnessConfig.js.map +1 -1
  32. package/dist/generators/outerLoopScripts.js +13 -0
  33. package/dist/generators/outerLoopScripts.js.map +1 -1
  34. package/dist/generators/permissionProfile.js +51 -9
  35. package/dist/generators/permissionProfile.js.map +1 -1
  36. package/dist/generators/projectSettings.js +5 -15
  37. package/dist/generators/projectSettings.js.map +1 -1
  38. package/dist/generators/repoRoot.js +130 -23
  39. package/dist/generators/repoRoot.js.map +1 -1
  40. package/dist/generators/scripts.js +4 -1
  41. package/dist/generators/scripts.js.map +1 -1
  42. package/dist/machine/paths.js +16 -4
  43. package/dist/machine/paths.js.map +1 -1
  44. package/dist/machine/plugins.js +2 -1
  45. package/dist/machine/plugins.js.map +1 -1
  46. package/dist/retrieval/chunk.js +158 -0
  47. package/dist/retrieval/chunk.js.map +1 -0
  48. package/dist/retrieval/corpus.js +75 -0
  49. package/dist/retrieval/corpus.js.map +1 -0
  50. package/dist/retrieval/models.js +175 -0
  51. package/dist/retrieval/models.js.map +1 -0
  52. package/dist/retrieval/queryLog.js +68 -0
  53. package/dist/retrieval/queryLog.js.map +1 -0
  54. package/dist/retrieval/refresh.js +55 -0
  55. package/dist/retrieval/refresh.js.map +1 -0
  56. package/dist/retrieval/runtime.js +171 -0
  57. package/dist/retrieval/runtime.js.map +1 -0
  58. package/dist/retrieval/search.js +118 -0
  59. package/dist/retrieval/search.js.map +1 -0
  60. package/dist/retrieval/server.js +197 -0
  61. package/dist/retrieval/server.js.map +1 -0
  62. package/dist/retrieval/session.js +41 -0
  63. package/dist/retrieval/session.js.map +1 -0
  64. package/dist/retrieval/setup.js +120 -0
  65. package/dist/retrieval/setup.js.map +1 -0
  66. package/dist/retrieval/store.js +170 -0
  67. package/dist/retrieval/store.js.map +1 -0
  68. package/package.json +22 -3
  69. package/templates/claude/CLAUDE.md +4 -4
  70. package/templates/claude/README.md +3 -1
  71. package/templates/claude/settings.autonomous.json +1 -1
  72. package/templates/claude/settings.autonomous.retrieval.json +9 -0
  73. package/templates/repo/README.md +2 -0
  74. package/templates/repo/gitignore +1 -0
  75. package/templates/repo/gitignore.retrieval +2 -0
  76. package/templates/repo/mcp.retrieval.json +11 -0
  77. package/templates/scripts/README.md +1 -1
  78. package/templates/scripts/autonomous-notify.sh +10 -4
  79. package/templates/scripts/autonomous-watcher.sh +336 -110
  80. package/templates/scripts/cleanup-merged-worktrees.sh +126 -8
  81. package/templates/scripts/docs-search-server.sh +64 -0
  82. package/templates/scripts/lib/harness-run-lib.sh +19 -3
  83. package/templates/scripts/restart-watcher.sh +4 -3
  84. package/templates/state-dir/business_parity_reviews/README.md +1 -1
  85. package/templates/state-dir/clarification_digests/README.md +1 -1
  86. package/templates/state-dir/clarifications/README.md +4 -4
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Splitting one Markdown document into the chunks docs retrieval embeds, indexes and cites.
3
+ *
4
+ * **The rule this module exists to enforce: a chunk's identity is its path and heading anchor, and
5
+ * its change signal is the hash of its text.** A refresh compares keys and hashes against the stored
6
+ * index and re-embeds only the chunks whose hash moved, so a key that shifted with an unrelated edit,
7
+ * or a hash over anything but the embedded text, would turn every refresh into a full rebuild or miss
8
+ * a change.
9
+ *
10
+ * A chunk starts at every `## ` or `### ` line outside a fenced code block, with two exceptions that
11
+ * exist to keep near-empty rows out of a fixed per-arm candidate budget: a `##` section with no body
12
+ * of its own that has at least one `###` child is folded into those children, and content before the
13
+ * first heading becomes the preamble chunk only when something but the title line is in it. A `###`
14
+ * section is otherwise its own chunk and is never folded into its parent `##`.
15
+ */
16
+ import { createHash } from 'node:crypto';
17
+ import { posix } from 'node:path';
18
+ const ATX_HEADING = /^(#{1,6})[ \t]+(.*?)(?:[ \t]+#+)?[ \t]*$/;
19
+ const FENCE_OPEN = /^ {0,3}(`{3,}|~{3,})/;
20
+ /**
21
+ * GitHub's heading slug, as the `#anchor` in a `path#heading` citation an agent then opens — so it
22
+ * has to land on the heading GitHub renders, not merely be unique.
23
+ *
24
+ * Lower-cases the text, drops every character that is not a letter, a digit, a space, a hyphen or an
25
+ * underscore (backticks included), and turns each space into `-`. `seen` holds the slugs already
26
+ * issued in this file: a repeat gets `-1`, `-2`, … in order, skipping any suffixed form a literal
27
+ * heading already took, so every slug in one file is distinct.
28
+ */
29
+ function headingSlug(text, seen) {
30
+ const base = text
31
+ .toLowerCase()
32
+ .replace(/[^\p{L}\p{N} _-]/gu, '')
33
+ .replace(/ /g, '-');
34
+ let slug = base;
35
+ if (seen.has(base)) {
36
+ let n = seen.get(base) ?? 0;
37
+ do {
38
+ n += 1;
39
+ slug = `${base}-${n}`;
40
+ } while (seen.has(slug));
41
+ seen.set(base, n);
42
+ }
43
+ seen.set(slug, 0);
44
+ return slug;
45
+ }
46
+ /** Trims blank lines from both ends and joins the rest. */
47
+ function joinBody(lines) {
48
+ const isBlank = (line) => line.trim() === '';
49
+ const start = lines.findIndex((line) => !isBlank(line));
50
+ if (start === -1)
51
+ return '';
52
+ let end = lines.length;
53
+ while (isBlank(lines[end - 1] ?? ''))
54
+ end -= 1;
55
+ return lines.slice(start, end).join('\n');
56
+ }
57
+ /** `anchor` is `undefined` for the preamble alone: a heading whose slug is empty still keys as `path#`. */
58
+ function makeChunk(path, anchor, heading, headingPath, title, body) {
59
+ const text = `${title}\n${headingPath}\n\n${body}`;
60
+ return {
61
+ key: anchor === undefined ? path : `${path}#${anchor}`,
62
+ path,
63
+ anchor: anchor ?? '',
64
+ heading,
65
+ text,
66
+ body,
67
+ hash: createHash('sha256').update(text).digest('hex'),
68
+ };
69
+ }
70
+ /**
71
+ * Splits `markdown`, read from the repo-relative `path`, into its chunks in document order.
72
+ *
73
+ * Every ATX heading outside a fence, at any level, consumes a slug, because GitHub numbers repeats
74
+ * across all levels; `##` and `###` start a chunk, except for the bodiless `##` wrapper the emission
75
+ * loop below folds into its `###` children. The title is the first `# ` line outside a fence, falling
76
+ * back to the file's basename.
77
+ */
78
+ export function chunkMarkdown(path, markdown) {
79
+ const lines = markdown.split(/\r?\n/);
80
+ const seen = new Map();
81
+ let current = { level: 0, heading: '', anchor: '', lines: [] };
82
+ const sections = [current];
83
+ let title;
84
+ let titleLineInPreamble = -1;
85
+ let fence;
86
+ for (const line of lines) {
87
+ if (fence !== undefined) {
88
+ const trimmed = line.trim();
89
+ if (trimmed.length >= fence.length && trimmed === fence.char.repeat(trimmed.length))
90
+ fence = undefined;
91
+ current.lines.push(line);
92
+ continue;
93
+ }
94
+ const open = FENCE_OPEN.exec(line);
95
+ if (open !== null) {
96
+ const marker = open[1] ?? '';
97
+ // A backtick fence's info string may not contain a backtick; such a line is not a fence.
98
+ if (!(marker.startsWith('`') && line.slice(line.indexOf(marker) + marker.length).includes('`'))) {
99
+ fence = { char: marker.charAt(0), length: marker.length };
100
+ }
101
+ current.lines.push(line);
102
+ continue;
103
+ }
104
+ const heading = ATX_HEADING.exec(line);
105
+ if (heading === null) {
106
+ current.lines.push(line);
107
+ continue;
108
+ }
109
+ const level = heading[1]?.length ?? 0;
110
+ const text = heading[2] ?? '';
111
+ const anchor = headingSlug(text, seen);
112
+ if (level === 2 || level === 3) {
113
+ current = { level, heading: text, anchor, lines: [] };
114
+ sections.push(current);
115
+ continue;
116
+ }
117
+ if (level === 1 && title === undefined) {
118
+ title = text;
119
+ if (sections.length === 1)
120
+ titleLineInPreamble = current.lines.length;
121
+ }
122
+ current.lines.push(line);
123
+ }
124
+ const resolvedTitle = title ?? posix.basename(path);
125
+ const chunks = [];
126
+ let parent;
127
+ for (const [index, section] of sections.entries()) {
128
+ if (section.level === 0) {
129
+ const own = section.lines.filter((_, index) => index !== titleLineInPreamble);
130
+ const body = joinBody(own);
131
+ if (body !== '')
132
+ chunks.push(makeChunk(path, undefined, '', resolvedTitle, resolvedTitle, body));
133
+ continue;
134
+ }
135
+ const body = joinBody(section.lines);
136
+ let headingPath;
137
+ if (section.level === 2) {
138
+ parent = `## ${section.heading}`;
139
+ headingPath = `${resolvedTitle} > ${parent}`;
140
+ // A `##` used only to group its `###` children carries no text of its own, so its row would be
141
+ // a near-duplicate of every other wrapper in the corpus while competing for the same fixed
142
+ // per-arm candidate budget. Folding it loses nothing: `headingPath` below spells the parent
143
+ // into each child, so the wrapper's words stay searchable through them.
144
+ //
145
+ // Decision, for the remaining case — an empty `##` or `###` with no child at all: it is still
146
+ // emitted. Unlike the wrapper, its words survive nowhere else in the index, so dropping it
147
+ // would make that heading unfindable; the fold above removes a duplicate, not merely a short row.
148
+ if (body === '' && sections[index + 1]?.level === 3)
149
+ continue;
150
+ }
151
+ else {
152
+ headingPath = parent === undefined ? `${resolvedTitle} > ### ${section.heading}` : `${resolvedTitle} > ${parent} > ### ${section.heading}`;
153
+ }
154
+ chunks.push(makeChunk(path, section.anchor, section.heading, headingPath, resolvedTitle, body));
155
+ }
156
+ return chunks;
157
+ }
158
+ //# sourceMappingURL=chunk.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunk.js","sourceRoot":"","sources":["../../src/retrieval/chunk.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAoBlC,MAAM,WAAW,GAAG,0CAA0C,CAAC;AAC/D,MAAM,UAAU,GAAG,sBAAsB,CAAC;AAE1C;;;;;;;;GAQG;AACH,SAAS,WAAW,CAAC,IAAY,EAAE,IAAyB;IAC1D,MAAM,IAAI,GAAG,IAAI;SACd,WAAW,EAAE;SACb,OAAO,CAAC,oBAAoB,EAAE,EAAE,CAAC;SACjC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IACtB,IAAI,IAAI,GAAG,IAAI,CAAC;IAChB,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;QACnB,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5B,GAAG,CAAC;YACF,CAAC,IAAI,CAAC,CAAC;YACP,IAAI,GAAG,GAAG,IAAI,IAAI,CAAC,EAAE,CAAC;QACxB,CAAC,QAAQ,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE;QACzB,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IACpB,CAAC;IACD,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAClB,OAAO,IAAI,CAAC;AACd,CAAC;AASD,2DAA2D;AAC3D,SAAS,QAAQ,CAAC,KAAwB;IACxC,MAAM,OAAO,GAAG,CAAC,IAAY,EAAW,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC;IAC9D,MAAM,KAAK,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IACxD,IAAI,KAAK,KAAK,CAAC,CAAC;QAAE,OAAO,EAAE,CAAC;IAC5B,IAAI,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC;IACvB,OAAO,OAAO,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;QAAE,GAAG,IAAI,CAAC,CAAC;IAC/C,OAAO,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC5C,CAAC;AAED,2GAA2G;AAC3G,SAAS,SAAS,CAAC,IAAY,EAAE,MAA0B,EAAE,OAAe,EAAE,WAAmB,EAAE,KAAa,EAAE,IAAY;IAC5H,MAAM,IAAI,GAAG,GAAG,KAAK,KAAK,WAAW,OAAO,IAAI,EAAE,CAAC;IACnD,OAAO;QACL,GAAG,EAAE,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,MAAM,EAAE;QACtD,IAAI;QACJ,MAAM,EAAE,MAAM,IAAI,EAAE;QACpB,OAAO;QACP,IAAI;QACJ,IAAI;QACJ,IAAI,EAAE,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;KACtD,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,aAAa,CAAC,IAAY,EAAE,QAAgB;IAC1D,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACtC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAkB,CAAC;IACvC,IAAI,OAAO,GAAY,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;IACxE,MAAM,QAAQ,GAAc,CAAC,OAAO,CAAC,CAAC;IACtC,IAAI,KAAyB,CAAC;IAC9B,IAAI,mBAAmB,GAAG,CAAC,CAAC,CAAC;IAC7B,IAAI,KAAmD,CAAC;IAExD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;YAC5B,IAAI,OAAO,CAAC,MAAM,IAAI,KAAK,CAAC,MAAM,IAAI,OAAO,KAAK,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC;gBAAE,KAAK,GAAG,SAAS,CAAC;YACvG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACzB,SAAS;QACX,CAAC;QACD,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;YAClB,MAAM,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YAC7B,yFAAyF;YACzF,IAAI,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;gBAChG,KAAK,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;YAC5D,CAAC;YACD,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACzB,SAAS;QACX,CAAC;QACD,MAAM,OAAO,GAAG,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACvC,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;YACrB,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACzB,SAAS;QACX,CAAC;QACD,MAAM,KAAK,GAAG,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,IAAI,CAAC,CAAC;QACtC,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC9B,MAAM,MAAM,GAAG,WAAW,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QACvC,IAAI,KAAK,KAAK,CAAC,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC;YAC/B,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;YACtD,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACvB,SAAS;QACX,CAAC;QACD,IAAI,KAAK,KAAK,CAAC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACvC,KAAK,GAAG,IAAI,CAAC;YACb,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;gBAAE,mBAAmB,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC;QACxE,CAAC;QACD,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC3B,CAAC;IAED,MAAM,aAAa,GAAG,KAAK,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IACpD,MAAM,MAAM,GAAe,EAAE,CAAC;IAC9B,IAAI,MAA0B,CAAC;IAC/B,KAAK,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC;QAClD,IAAI,OAAO,CAAC,KAAK,KAAK,CAAC,EAAE,CAAC;YACxB,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,mBAAmB,CAAC,CAAC;YAC9E,MAAM,IAAI,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;YAC3B,IAAI,IAAI,KAAK,EAAE;gBAAE,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,aAAa,EAAE,aAAa,EAAE,IAAI,CAAC,CAAC,CAAC;YACjG,SAAS;QACX,CAAC;QACD,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QACrC,IAAI,WAAmB,CAAC;QACxB,IAAI,OAAO,CAAC,KAAK,KAAK,CAAC,EAAE,CAAC;YACxB,MAAM,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;YACjC,WAAW,GAAG,GAAG,aAAa,MAAM,MAAM,EAAE,CAAC;YAC7C,+FAA+F;YAC/F,2FAA2F;YAC3F,4FAA4F;YAC5F,wEAAwE;YACxE,EAAE;YACF,8FAA8F;YAC9F,2FAA2F;YAC3F,kGAAkG;YAClG,IAAI,IAAI,KAAK,EAAE,IAAI,QAAQ,CAAC,KAAK,GAAG,CAAC,CAAC,EAAE,KAAK,KAAK,CAAC;gBAAE,SAAS;QAChE,CAAC;aAAM,CAAC;YACN,WAAW,GAAG,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,aAAa,UAAU,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,GAAG,aAAa,MAAM,MAAM,UAAU,OAAO,CAAC,OAAO,EAAE,CAAC;QAC7I,CAAC;QACD,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,IAAI,CAAC,CAAC,CAAC;IAClG,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Enumerating the files docs retrieval indexes.
3
+ *
4
+ * **The rule this module exists to enforce: the corpus is exactly every Markdown file under
5
+ * `docs.root` plus every conventions document `layers[]` names — nothing else, and no copy of that
6
+ * list elsewhere.** The gate (`retrievalApplies` in `cli/src/config/model.ts`) is its callers' to
7
+ * check, not this module's.
8
+ *
9
+ * Nothing here throws on a missing path: the corpus is navigation, so an absent `docs.root`, a missing
10
+ * conventions document or a path resolving outside the repository becomes a warning line and the
11
+ * rest of the corpus is still returned.
12
+ */
13
+ import { readdirSync, statSync } from 'node:fs';
14
+ import { join, relative, sep } from 'node:path';
15
+ import { insideRepo } from '../core/paths.js';
16
+ import { normalizeRepoDir } from '../core/repoPaths.js';
17
+ function toRepoRelative(repoRoot, absolute) {
18
+ return relative(repoRoot, absolute).split(sep).join('/');
19
+ }
20
+ /** Every `*.md` under `dir`, recursively; a symlinked directory is not descended into. */
21
+ function markdownUnder(dir, out) {
22
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
23
+ const full = join(dir, entry.name);
24
+ if (entry.isDirectory()) {
25
+ markdownUnder(full, out);
26
+ continue;
27
+ }
28
+ if (!entry.name.endsWith('.md'))
29
+ continue;
30
+ if (entry.isFile() || (entry.isSymbolicLink() && statSync(full, { throwIfNoEntry: false })?.isFile() === true)) {
31
+ out.push(full);
32
+ }
33
+ }
34
+ }
35
+ /** The corpus of `config` in the repository at `repoRoot`. Callers check `retrievalApplies` first. */
36
+ export function corpusFiles(repoRoot, config) {
37
+ const absolute = [];
38
+ const warnings = [];
39
+ const root = config.docs?.root;
40
+ if (root === undefined) {
41
+ warnings.push('docs.root is not set, so no documentation directory is indexed');
42
+ }
43
+ else {
44
+ const dir = normalizeRepoDir(root);
45
+ const full = join(repoRoot, dir);
46
+ if (!insideRepo(repoRoot, full)) {
47
+ warnings.push(`docs.root ${dir} resolves outside the repository and was skipped`);
48
+ }
49
+ else if (statSync(full, { throwIfNoEntry: false })?.isDirectory() !== true) {
50
+ warnings.push(`docs.root ${dir} is not a directory, so no documentation directory is indexed`);
51
+ }
52
+ else {
53
+ markdownUnder(full, absolute);
54
+ }
55
+ }
56
+ for (const layer of config.layers ?? []) {
57
+ const conventions = layer.conventions;
58
+ if (typeof conventions !== 'string' || conventions === '')
59
+ continue;
60
+ const path = normalizeRepoDir(conventions);
61
+ const full = join(repoRoot, path);
62
+ if (!insideRepo(repoRoot, full)) {
63
+ warnings.push(`conventions document ${path} of layer ${layer.name} resolves outside the repository and was skipped`);
64
+ }
65
+ else if (statSync(full, { throwIfNoEntry: false })?.isFile() !== true) {
66
+ warnings.push(`conventions document ${path} of layer ${layer.name} is missing and was skipped`);
67
+ }
68
+ else {
69
+ absolute.push(full);
70
+ }
71
+ }
72
+ const files = [...new Set(absolute.map((path) => toRepoRelative(repoRoot, path)))].sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
73
+ return { files, warnings };
74
+ }
75
+ //# sourceMappingURL=corpus.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"corpus.js","sourceRoot":"","sources":["../../src/retrieval/corpus.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,175 @@
1
+ /**
2
+ * The embedder and the reranker docs retrieval runs on: the two interfaces every consumer takes, the
3
+ * local Transformers.js models behind them, the files an offline load needs, and the deterministic
4
+ * stubs.
5
+ *
6
+ * **The rule this module exists to enforce: a model is downloaded only by `fetchModels`, at setup
7
+ * time. Every other load runs with remote models disabled, so a run never reaches the network.**
8
+ * `env.allowRemoteModels` is set in {@link loadModels} alone, from its caller's option, before any
9
+ * `from_pretrained`; `@huggingface/transformers` is reached only through `loadRetrievalModule`
10
+ * (`cli/src/retrieval/runtime.ts`), and this file takes its types with `import type`.
11
+ *
12
+ * **Why the stubs exist.** The suite drives the compiled CLI in a subprocess and has no mocking
13
+ * framework (`.claude/context/conventions.md` → `## The testing bar`), so a test cannot substitute a
14
+ * model in process. {@link RETRIEVAL_STUB_ENV} is the seam: set in the child's environment, it swaps
15
+ * both halves for hash-based stand-ins that never download or load a model.
16
+ */
17
+ import { createHash } from 'node:crypto';
18
+ import { existsSync } from 'node:fs';
19
+ import { join } from 'node:path';
20
+ import { HarnessError } from '../core/errors.js';
21
+ import { loadRetrievalModule, retrievalModelCacheDir } from './runtime.js';
22
+ /**
23
+ * ONNX port of `BAAI/bge-small-en-v1.5`, MIT; about 33M parameters, per the upstream model card.
24
+ * Loaded with `dtype: 'q8'`, CLS pooling and `normalize: true`, yielding {@link EMBEDDING_DIMENSIONS}
25
+ * dimensions.
26
+ */
27
+ export const EMBEDDING_MODEL = 'Xenova/bge-small-en-v1.5';
28
+ /** bge-v1.5's retrieval instruction, prepended to queries only. */
29
+ const EMBEDDING_QUERY_PREFIX = 'Represent this sentence for searching relevant passages: ';
30
+ /**
31
+ * ONNX port of `cross-encoder/ms-marco-MiniLM-L-6-v2`, Apache-2.0; about 23M parameters, per the
32
+ * upstream model card. Loaded with `dtype: 'q8'`; each score is the sigmoid of its single logit.
33
+ */
34
+ export const RERANK_MODEL = 'Xenova/ms-marco-MiniLM-L-6-v2';
35
+ /** Bumped by hand when any loading parameter above changes, so the embedder's `id` changes with it. */
36
+ const MODEL_VERSION = 1;
37
+ /** The vector width of {@link EMBEDDING_MODEL}, which the stub embedder matches. */
38
+ const EMBEDDING_DIMENSIONS = 384;
39
+ const MODEL_DTYPE = 'q8';
40
+ const TRANSFORMERS_SPECIFIER = '@huggingface/transformers';
41
+ /**
42
+ * The files, relative to `<retrievalModelCacheDir()>/<model id>/`, an offline load of each model
43
+ * needs. Read off `@huggingface/transformers` 4.3.0: `utils/hub.js` → `buildResourcePaths` keys a
44
+ * `FileCache` entry at `<model id>/<filename>` on the `main` revision, and `FileCache.match` joins that
45
+ * onto `env.cacheDir`; `utils/model_registry/get_model_files.js` → `get_model_files` lists
46
+ * `config.json` plus `onnx/<session><dtype suffix>.onnx` (`q8` → `_quantized`, `utils/dtypes.js`),
47
+ * with no external-data chunk for these configs; `get_tokenizer_files` lists `tokenizer.json` and
48
+ * `tokenizer_config.json`.
49
+ */
50
+ export const MODEL_FILES = {
51
+ [EMBEDDING_MODEL]: ['config.json', 'tokenizer.json', 'tokenizer_config.json', 'onnx/model_quantized.onnx'],
52
+ [RERANK_MODEL]: ['config.json', 'tokenizer.json', 'tokenizer_config.json', 'onnx/model_quantized.onnx'],
53
+ };
54
+ /** File existence only: every {@link MODEL_FILES} entry under `cacheDir`, `missing` as `<model id>/<file>`. */
55
+ export function modelFilesPresent(cacheDir) {
56
+ const missing = [];
57
+ for (const [modelId, files] of Object.entries(MODEL_FILES)) {
58
+ for (const file of files) {
59
+ if (!existsSync(join(cacheDir, modelId, file)))
60
+ missing.push(`${modelId}/${file}`);
61
+ }
62
+ }
63
+ return { present: missing.length === 0, missing };
64
+ }
65
+ /** The real models. Remote loading follows `options.allowRemote`, set before anything is loaded. */
66
+ async function loadModels(options) {
67
+ const transformers = await loadRetrievalModule(TRANSFORMERS_SPECIFIER);
68
+ const { env } = transformers;
69
+ env.cacheDir = retrievalModelCacheDir();
70
+ env.allowRemoteModels = options.allowRemote;
71
+ const extractor = await transformers.pipeline('feature-extraction', EMBEDDING_MODEL, { dtype: MODEL_DTYPE });
72
+ const embed = async (texts) => {
73
+ const output = (await extractor(texts, { pooling: 'cls', normalize: true }));
74
+ return output.tolist();
75
+ };
76
+ const embedder = {
77
+ id: `${EMBEDDING_MODEL}:${MODEL_DTYPE}:cls:${EMBEDDING_DIMENSIONS}:v${MODEL_VERSION}`,
78
+ dimensions: EMBEDDING_DIMENSIONS,
79
+ async embedDocuments(texts) {
80
+ return texts.length === 0 ? [] : embed([...texts]);
81
+ },
82
+ async embedQuery(text) {
83
+ const [vector] = await embed([`${EMBEDDING_QUERY_PREFIX}${text}`]);
84
+ return vector ?? [];
85
+ },
86
+ };
87
+ const tokenizer = await transformers.AutoTokenizer.from_pretrained(RERANK_MODEL);
88
+ const model = await transformers.AutoModelForSequenceClassification.from_pretrained(RERANK_MODEL, {
89
+ dtype: MODEL_DTYPE,
90
+ });
91
+ const reranker = {
92
+ id: `${RERANK_MODEL}:${MODEL_DTYPE}:sigmoid:v${MODEL_VERSION}`,
93
+ async score(query, passages) {
94
+ if (passages.length === 0)
95
+ return [];
96
+ const inputs = tokenizer(passages.map(() => query), { text_pair: [...passages], padding: true, truncation: true });
97
+ const { logits } = (await model(inputs));
98
+ return logits.sigmoid().tolist().map((row) => row[0] ?? 0);
99
+ },
100
+ };
101
+ return { embedder, reranker };
102
+ }
103
+ /** The one place a download is allowed: load both models with remote loading on, and run each once. */
104
+ export async function fetchModels() {
105
+ const { embedder, reranker } = await loadModels({ allowRemote: true });
106
+ await embedder.embedQuery('warm up');
107
+ await reranker.score('warm up', ['warm up']);
108
+ }
109
+ /** The environment variable that selects the stubs; this constant is its only spelling in `cli/src`. */
110
+ export const RETRIEVAL_STUB_ENV = 'AUTONOMOUS_SDLC_HARNESS_RETRIEVAL_STUB';
111
+ /** {@link RETRIEVAL_STUB_ENV}'s value, unset read as empty; the only read of the variable in `cli/src`. */
112
+ function stubEnvValue() {
113
+ return process.env[RETRIEVAL_STUB_ENV] ?? '';
114
+ }
115
+ /** True when {@link RETRIEVAL_STUB_ENV} is set to a non-empty value — the one reading of "a stub run". */
116
+ export function stubModelsSelected() {
117
+ return stubEnvValue() !== '';
118
+ }
119
+ const STUB_VERSIONS = ['hash-v1', 'hash-v2'];
120
+ /** Lower-cased alphanumeric runs of at least `minLength` characters. */
121
+ function stubTokens(text, minLength) {
122
+ return text
123
+ .toLowerCase()
124
+ .split(/[^a-z0-9]+/)
125
+ .filter((token) => token.length >= minLength);
126
+ }
127
+ function stubEmbedder(version) {
128
+ const salt = version === 'hash-v2' ? 'v2' : '';
129
+ const embed = (text) => {
130
+ const vector = new Array(EMBEDDING_DIMENSIONS).fill(0);
131
+ for (const token of stubTokens(text, 2)) {
132
+ const bucket = createHash('sha256').update(salt).update(token).digest().readUInt32BE(0) % EMBEDDING_DIMENSIONS;
133
+ vector[bucket] = (vector[bucket] ?? 0) + 1;
134
+ }
135
+ const norm = Math.sqrt(vector.reduce((sum, value) => sum + value * value, 0));
136
+ return norm === 0 ? vector : vector.map((value) => value / norm);
137
+ };
138
+ return {
139
+ id: `stub-hash:${version}`,
140
+ dimensions: EMBEDDING_DIMENSIONS,
141
+ async embedDocuments(texts) {
142
+ return texts.map(embed);
143
+ },
144
+ async embedQuery(text) {
145
+ return embed(text);
146
+ },
147
+ };
148
+ }
149
+ const STUB_RERANKER = {
150
+ id: 'stub-overlap',
151
+ async score(query, passages) {
152
+ const queryTokens = [...new Set(stubTokens(query, 3))];
153
+ return passages.map((passage) => {
154
+ if (queryTokens.length === 0)
155
+ return 0;
156
+ const passageTokens = new Set(stubTokens(passage, 2));
157
+ return queryTokens.filter((token) => passageTokens.has(token)).length / queryTokens.length;
158
+ });
159
+ },
160
+ };
161
+ /**
162
+ * The stubs when {@link RETRIEVAL_STUB_ENV} names one of {@link STUB_VERSIONS}, a refusal for any other
163
+ * non-empty value, and otherwise {@link loadModels}.
164
+ */
165
+ export async function resolveModels(options) {
166
+ if (!stubModelsSelected())
167
+ return loadModels(options);
168
+ const value = stubEnvValue();
169
+ const version = STUB_VERSIONS.find((candidate) => candidate === value);
170
+ if (version === undefined) {
171
+ throw new HarnessError(`${RETRIEVAL_STUB_ENV} is set to ${JSON.stringify(value)}; its legal values are ${STUB_VERSIONS.join(' and ')}, or unset it to load the real models`);
172
+ }
173
+ return { embedder: stubEmbedder(version), reranker: STUB_RERANKER };
174
+ }
175
+ //# sourceMappingURL=models.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"models.js","sourceRoot":"","sources":["../../src/retrieval/models.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * The `search_docs` query log: the environment variable that turns it on, the shape of one JSONL
3
+ * record, and the append that writes it.
4
+ *
5
+ * **The rule this module exists to enforce: failing to log never fails a call, and nothing here ever
6
+ * reaches stdout.** {@link logQuery} absorbs its own failure into a `Reporter` warning on stderr and
7
+ * returns, so `cli/src/retrieval/server.ts` → `answer` has nothing to catch and returns its result
8
+ * unchanged. Stdout belongs to the MCP transport, where a stray byte is a malformed frame for the
9
+ * client (`cli/templates/scripts/docs-search-server.sh`, its stdout paragraph).
10
+ *
11
+ * **Why the record shape lives here rather than in its one caller.** {@link QueryLogRecord} and
12
+ * {@link QueryOutcome} are the input contract the `feat_docs_retrieval_eval` branch reads: the feature
13
+ * ships opt-in and not yet measured against the index-first navigation agents use today, and this file
14
+ * is what distinguishes a tool no agent reaches for from one agents reach for and get nothing from. A
15
+ * contract inlined into its first caller has no owner file a later reader can find, so `server.ts`
16
+ * holds no field list, no outcome spelling and no `fs` call of its own.
17
+ *
18
+ * **One key set, always.** Every record carries every key, the outcome included; a field a failed exit
19
+ * cannot fill is `null` rather than absent, so a consumer never infers the outcome from which other
20
+ * fields are missing.
21
+ *
22
+ * **No locking, deliberately.** `cli/src/retrieval/server.ts` → `serveDocs` serializes calls through
23
+ * its `queue`, so two appends cannot interleave. Do not add any.
24
+ *
25
+ * **No shipped template mirrors {@link RETRIEVAL_LOG_ENV}, and that is deliberate.** The agent runner
26
+ * starts the server from `.mcp.json` and passes only the MCP SDK's fixed inherited set —
27
+ * `getDefaultEnvironment()` in `@modelcontextprotocol/sdk/client/stdio.js` is `HOME`, `LOGNAME`,
28
+ * `PATH`, `SHELL` and `USER` on POSIX — so an exported variable does not reach the child. The route is
29
+ * an `env` entry the operator adds by hand to the `harness-docs` server in their own `.mcp.json`,
30
+ * which `init` merges rather than overwrites. `init` must not generate the key: generating it would
31
+ * make this a surface an adopter who wants nothing has to read. So the spelling lives here and in the
32
+ * adopter's own hand-edited file only, never under `cli/templates/`, and there is no mirror for this
33
+ * header to declare under `.claude/context/conventions.md` → `## What accompanies a new unit of each
34
+ * kind` (row *A persisted machine-state key*).
35
+ *
36
+ * The write itself is outside the write engine's monopoly; `cli/src/core/writer.ts`'s header
37
+ * enumerates it as the third such exception.
38
+ */
39
+ import { appendFileSync } from 'node:fs';
40
+ /**
41
+ * The environment variable that turns the log on; this constant is its only spelling in `cli/src`.
42
+ * Its value is a path to a file: set and non-empty, one JSON line is appended per `search_docs` call;
43
+ * unset or empty, nothing is opened, nothing is written and no code path changes. It is deliberately
44
+ * not a `harness.config.json` key — an adopter who does not set it gains no new surface, so there is
45
+ * nothing for `config`, `doctor` or the config check to grow.
46
+ */
47
+ export const RETRIEVAL_LOG_ENV = 'AUTONOMOUS_SDLC_HARNESS_RETRIEVAL_LOG';
48
+ /** {@link RETRIEVAL_LOG_ENV}'s value, unset read as empty; the only read of the variable in `cli/src`. */
49
+ function logPath() {
50
+ return process.env[RETRIEVAL_LOG_ENV] ?? '';
51
+ }
52
+ /**
53
+ * Append `record` as one JSON line to the file {@link RETRIEVAL_LOG_ENV} names, or do nothing when the
54
+ * variable is unset or empty. Never throws: an unwritable path, a missing parent directory or a full
55
+ * disk becomes one `report.warn` and a return.
56
+ */
57
+ export function logQuery(record, report) {
58
+ const path = logPath();
59
+ if (path === '')
60
+ return;
61
+ try {
62
+ appendFileSync(path, `${JSON.stringify(record)}\n`, 'utf8');
63
+ }
64
+ catch (error) {
65
+ report.warn(`${RETRIEVAL_LOG_ENV}: appending to ${path} failed: ${error instanceof Error ? error.message : String(error)}; the query was answered and nothing was logged`);
66
+ }
67
+ }
68
+ //# sourceMappingURL=queryLog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queryLog.js","sourceRoot":"","sources":["../../src/retrieval/queryLog.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAIzC;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,uCAAuC,CAAC;AA8BzE,0GAA0G;AAC1G,SAAS,OAAO;IACd,OAAO,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,IAAI,EAAE,CAAC;AAC9C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,QAAQ,CAAC,MAAsB,EAAE,MAAgB;IAC/D,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;IACvB,IAAI,IAAI,KAAK,EAAE;QAAE,OAAO;IACxB,IAAI,CAAC;QACH,cAAc,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAC9D,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,CAAC,IAAI,CACT,GAAG,iBAAiB,kBAAkB,IAAI,YAAY,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,iDAAiD,CAC9J,CAAC;IACJ,CAAC;AACH,CAAC"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Bringing the docs-retrieval index in line with the corpus, incrementally.
3
+ *
4
+ * **The rule this module exists to enforce: the Markdown is the source of truth and the index is
5
+ * always rebuildable from it, so nothing stored here is authoritative.** A refresh never keeps a chunk
6
+ * the corpus no longer has, and an embedder change discards every stored vector rather than mixing
7
+ * two models' vectors in one index.
8
+ */
9
+ import { readFileSync } from 'node:fs';
10
+ import { join } from 'node:path';
11
+ import { chunkMarkdown } from './chunk.js';
12
+ import { corpusFiles } from './corpus.js';
13
+ import { EMBEDDER_META_KEY } from './store.js';
14
+ const EMBED_BATCH_SIZE = 32;
15
+ /**
16
+ * Refreshes `store` from the corpus of `config`: (1) an embedder id differing from the stored one,
17
+ * or none stored, clears the index and records the new id; (2) every corpus file is chunked; (3) every
18
+ * stored key not in that set is deleted; (4) chunks whose key is new or whose hash moved are embedded
19
+ * in batches of 32 and upserted; (5) the rest count as unchanged.
20
+ *
21
+ * Expected cost: a document whose title changed re-embeds every one of its chunks, because the title
22
+ * is part of each chunk's embedded `text`.
23
+ */
24
+ export async function refreshIndex(options) {
25
+ const { repoRoot, config, store, embedder } = options;
26
+ let rebuilt = false;
27
+ const storedEmbedder = await store.readMeta(EMBEDDER_META_KEY);
28
+ if (storedEmbedder !== embedder.id) {
29
+ await store.clear();
30
+ await store.writeMeta(EMBEDDER_META_KEY, embedder.id);
31
+ rebuilt = storedEmbedder !== undefined;
32
+ }
33
+ const corpus = corpusFiles(repoRoot, config);
34
+ const chunks = corpus.files.flatMap((path) => chunkMarkdown(path, readFileSync(join(repoRoot, path), 'utf8')));
35
+ const stored = await store.listChunkHashes();
36
+ const current = new Set(chunks.map((chunk) => chunk.key));
37
+ const gone = [...stored.keys()].filter((key) => !current.has(key));
38
+ await store.deleteChunks(gone);
39
+ const changed = chunks.filter((chunk) => stored.get(chunk.key) !== chunk.hash);
40
+ for (let start = 0; start < changed.length; start += EMBED_BATCH_SIZE) {
41
+ const batch = changed.slice(start, start + EMBED_BATCH_SIZE);
42
+ const embeddings = await embedder.embedDocuments(batch.map((chunk) => chunk.text));
43
+ await store.upsertChunks(batch, embeddings);
44
+ }
45
+ return {
46
+ files: corpus.files.length,
47
+ chunks: chunks.length,
48
+ embedded: changed.length,
49
+ unchanged: chunks.length - changed.length,
50
+ deleted: gone.length,
51
+ rebuilt,
52
+ warnings: corpus.warnings,
53
+ };
54
+ }
55
+ //# sourceMappingURL=refresh.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"refresh.js","sourceRoot":"","sources":["../../src/retrieval/refresh.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAGjC,OAAO,EAAE,aAAa,EAAiB,MAAM,YAAY,CAAC;AAC1D,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,OAAO,EAAE,iBAAiB,EAAiB,MAAM,YAAY,CAAC;AAe9D,MAAM,gBAAgB,GAAG,EAAE,CAAC;AAE5B;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,OAKlC;IACC,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC;IAEtD,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,MAAM,cAAc,GAAG,MAAM,KAAK,CAAC,QAAQ,CAAC,iBAAiB,CAAC,CAAC;IAC/D,IAAI,cAAc,KAAK,QAAQ,CAAC,EAAE,EAAE,CAAC;QACnC,MAAM,KAAK,CAAC,KAAK,EAAE,CAAC;QACpB,MAAM,KAAK,CAAC,SAAS,CAAC,iBAAiB,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC;QACtD,OAAO,GAAG,cAAc,KAAK,SAAS,CAAC;IACzC,CAAC;IAED,MAAM,MAAM,GAAG,WAAW,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC7C,MAAM,MAAM,GAAe,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,YAAY,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;IAE3H,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,eAAe,EAAE,CAAC;IAC7C,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;IAC1D,MAAM,IAAI,GAAG,CAAC,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;IACnE,MAAM,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;IAE/B,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,CAAC;IAC/E,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,OAAO,CAAC,MAAM,EAAE,KAAK,IAAI,gBAAgB,EAAE,CAAC;QACtE,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,GAAG,gBAAgB,CAAC,CAAC;QAC7D,MAAM,UAAU,GAAG,MAAM,QAAQ,CAAC,cAAc,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;QACnF,MAAM,KAAK,CAAC,YAAY,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;IAC9C,CAAC;IAED,OAAO;QACL,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM;QAC1B,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,QAAQ,EAAE,OAAO,CAAC,MAAM;QACxB,SAAS,EAAE,MAAM,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM;QACzC,OAAO,EAAE,IAAI,CAAC,MAAM;QACpB,OAAO;QACP,QAAQ,EAAE,MAAM,CAAC,QAAQ;KAC1B,CAAC;AACJ,CAAC"}