@gitwhy-cli/whyspec 0.1.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.
- package/LICENSE +21 -0
- package/README.md +113 -0
- package/dist/adapters/agents-md.d.ts +13 -0
- package/dist/adapters/agents-md.d.ts.map +1 -0
- package/dist/adapters/agents-md.js +165 -0
- package/dist/adapters/agents-md.js.map +1 -0
- package/dist/adapters/claude-code.d.ts +13 -0
- package/dist/adapters/claude-code.d.ts.map +1 -0
- package/dist/adapters/claude-code.js +206 -0
- package/dist/adapters/claude-code.js.map +1 -0
- package/dist/adapters/cursor.d.ts +12 -0
- package/dist/adapters/cursor.d.ts.map +1 -0
- package/dist/adapters/cursor.js +220 -0
- package/dist/adapters/cursor.js.map +1 -0
- package/dist/adapters/types.d.ts +16 -0
- package/dist/adapters/types.d.ts.map +1 -0
- package/dist/adapters/types.js +19 -0
- package/dist/adapters/types.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +109 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/commands/capture.d.ts +18 -0
- package/dist/commands/capture.d.ts.map +1 -0
- package/dist/commands/capture.js +85 -0
- package/dist/commands/capture.js.map +1 -0
- package/dist/commands/debug.d.ts +16 -0
- package/dist/commands/debug.d.ts.map +1 -0
- package/dist/commands/debug.js +74 -0
- package/dist/commands/debug.js.map +1 -0
- package/dist/commands/execute.d.ts +36 -0
- package/dist/commands/execute.d.ts.map +1 -0
- package/dist/commands/execute.js +110 -0
- package/dist/commands/execute.js.map +1 -0
- package/dist/commands/init.d.ts +14 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +166 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/list.d.ts +23 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +95 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/plan.d.ts +20 -0
- package/dist/commands/plan.d.ts.map +1 -0
- package/dist/commands/plan.js +48 -0
- package/dist/commands/plan.js.map +1 -0
- package/dist/commands/search.d.ts +12 -0
- package/dist/commands/search.d.ts.map +1 -0
- package/dist/commands/search.js +36 -0
- package/dist/commands/search.js.map +1 -0
- package/dist/commands/show.d.ts +25 -0
- package/dist/commands/show.d.ts.map +1 -0
- package/dist/commands/show.js +145 -0
- package/dist/commands/show.js.map +1 -0
- package/dist/commands/status.d.ts +29 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +125 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/template.d.ts +14 -0
- package/dist/commands/template.d.ts.map +1 -0
- package/dist/commands/template.js +25 -0
- package/dist/commands/template.js.map +1 -0
- package/dist/core/categorize.d.ts +30 -0
- package/dist/core/categorize.d.ts.map +1 -0
- package/dist/core/categorize.js +72 -0
- package/dist/core/categorize.js.map +1 -0
- package/dist/core/config.d.ts +26 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +52 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/context.d.ts +27 -0
- package/dist/core/context.d.ts.map +1 -0
- package/dist/core/context.js +75 -0
- package/dist/core/context.js.map +1 -0
- package/dist/core/search.d.ts +51 -0
- package/dist/core/search.d.ts.map +1 -0
- package/dist/core/search.js +235 -0
- package/dist/core/search.js.map +1 -0
- package/dist/core/storage.d.ts +36 -0
- package/dist/core/storage.d.ts.map +1 -0
- package/dist/core/storage.js +80 -0
- package/dist/core/storage.js.map +1 -0
- package/dist/core/templates.d.ts +28 -0
- package/dist/core/templates.d.ts.map +1 -0
- package/dist/core/templates.js +176 -0
- package/dist/core/templates.js.map +1 -0
- package/dist/ui/ascii-logo.d.ts +7 -0
- package/dist/ui/ascii-logo.d.ts.map +1 -0
- package/dist/ui/ascii-logo.js +15 -0
- package/dist/ui/ascii-logo.js.map +1 -0
- package/dist/ui/tool-picker.d.ts +13 -0
- package/dist/ui/tool-picker.d.ts.map +1 -0
- package/dist/ui/tool-picker.js +76 -0
- package/dist/ui/tool-picker.js.map +1 -0
- package/dist/ui/welcome.d.ts +4 -0
- package/dist/ui/welcome.d.ts.map +1 -0
- package/dist/ui/welcome.js +43 -0
- package/dist/ui/welcome.js.map +1 -0
- package/dist/utils/changes.d.ts +19 -0
- package/dist/utils/changes.d.ts.map +1 -0
- package/dist/utils/changes.js +63 -0
- package/dist/utils/changes.js.map +1 -0
- package/dist/utils/git.d.ts +28 -0
- package/dist/utils/git.d.ts.map +1 -0
- package/dist/utils/git.js +104 -0
- package/dist/utils/git.js.map +1 -0
- package/dist/utils/slugify.d.ts +12 -0
- package/dist/utils/slugify.d.ts.map +1 -0
- package/dist/utils/slugify.js +21 -0
- package/dist/utils/slugify.js.map +1 -0
- package/dist/utils/telemetry.d.ts +21 -0
- package/dist/utils/telemetry.d.ts.map +1 -0
- package/dist/utils/telemetry.js +32 -0
- package/dist/utils/telemetry.js.map +1 -0
- package/package.json +81 -0
- package/skills/whyspec-capture/SKILL.md +154 -0
- package/skills/whyspec-debug/SKILL.md +404 -0
- package/skills/whyspec-execute/SKILL.md +118 -0
- package/skills/whyspec-plan/SKILL.md +170 -0
- package/skills/whyspec-search/SKILL.md +69 -0
- package/skills/whyspec-show/SKILL.md +90 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context generation for WhySpec capture command.
|
|
3
|
+
* - Context ID: ctx_ + 8-char alphanumeric
|
|
4
|
+
* - SaaS XML format (GitWhy compatible)
|
|
5
|
+
* - Decision Bridge extraction from design.md
|
|
6
|
+
*/
|
|
7
|
+
import { randomBytes } from "node:crypto";
|
|
8
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { contextTemplate } from "./templates.js";
|
|
11
|
+
/**
|
|
12
|
+
* Generate a context ID: ctx_ + 8 random alphanumeric characters.
|
|
13
|
+
* Matches GitWhy convention (FR-20).
|
|
14
|
+
*/
|
|
15
|
+
export function generateContextId() {
|
|
16
|
+
const chars = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
17
|
+
const bytes = randomBytes(8);
|
|
18
|
+
let id = "";
|
|
19
|
+
for (let i = 0; i < 8; i++) {
|
|
20
|
+
id += chars[bytes[i] % chars.length];
|
|
21
|
+
}
|
|
22
|
+
return `ctx_${id}`;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Render the SaaS-compatible XML context template.
|
|
26
|
+
*/
|
|
27
|
+
export function renderContextXml() {
|
|
28
|
+
return contextTemplate();
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Extract decision items from design.md's "## Decisions to Make" section.
|
|
32
|
+
* Parses checkbox lines: `- [ ] description` and `- [x] description`.
|
|
33
|
+
* Returns the description text of each decision item.
|
|
34
|
+
*/
|
|
35
|
+
export function extractDecisions(designContent) {
|
|
36
|
+
const lines = designContent.split("\n");
|
|
37
|
+
const decisions = [];
|
|
38
|
+
let inSection = false;
|
|
39
|
+
for (const line of lines) {
|
|
40
|
+
// Start capturing when we hit the Decisions to Make heading
|
|
41
|
+
if (/^##\s+Decisions to Make/i.test(line)) {
|
|
42
|
+
inSection = true;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
// Stop at the next heading
|
|
46
|
+
if (inSection && /^##\s/.test(line)) {
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
// Capture checkbox items within the section
|
|
50
|
+
if (inSection) {
|
|
51
|
+
const match = line.match(/^-\s+\[[ xX]\]\s+(.+)/);
|
|
52
|
+
if (match) {
|
|
53
|
+
decisions.push(match[1].trim());
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return decisions;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Get the start time of a change (used for commit range detection).
|
|
61
|
+
* Reads .started file written by `whyspec plan`. Falls back to folder birthtime.
|
|
62
|
+
*/
|
|
63
|
+
export function getChangeFolderCreatedAt(changePath) {
|
|
64
|
+
const startedFile = join(changePath, ".started");
|
|
65
|
+
if (existsSync(startedFile)) {
|
|
66
|
+
const ts = readFileSync(startedFile, "utf-8").trim();
|
|
67
|
+
const parsed = new Date(ts);
|
|
68
|
+
if (!isNaN(parsed.getTime()))
|
|
69
|
+
return parsed;
|
|
70
|
+
}
|
|
71
|
+
// Fallback to folder birthtime (less reliable across platforms)
|
|
72
|
+
const stat = statSync(changePath);
|
|
73
|
+
return stat.birthtime;
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/core/context.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC7D,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEjD;;;GAGG;AACH,MAAM,UAAU,iBAAiB;IAC/B,MAAM,KAAK,GAAG,sCAAsC,CAAC;IACrD,MAAM,KAAK,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC;IAC7B,IAAI,EAAE,GAAG,EAAE,CAAC;IACZ,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC3B,EAAE,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;IACvC,CAAC;IACD,OAAO,OAAO,EAAE,EAAE,CAAC;AACrB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB;IAC9B,OAAO,eAAe,EAAE,CAAC;AAC3B,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,aAAqB;IACpD,MAAM,KAAK,GAAG,aAAa,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACxC,MAAM,SAAS,GAAa,EAAE,CAAC;IAC/B,IAAI,SAAS,GAAG,KAAK,CAAC;IAEtB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,4DAA4D;QAC5D,IAAI,0BAA0B,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAC1C,SAAS,GAAG,IAAI,CAAC;YACjB,SAAS;QACX,CAAC;QACD,2BAA2B;QAC3B,IAAI,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACpC,MAAM;QACR,CAAC;QACD,4CAA4C;QAC5C,IAAI,SAAS,EAAE,CAAC;YACd,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAC;YAClD,IAAI,KAAK,EAAE,CAAC;gBACV,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;YAClC,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,wBAAwB,CAAC,UAAkB;IACzD,MAAM,WAAW,GAAG,IAAI,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;IACjD,IAAI,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;QAC5B,MAAM,EAAE,GAAG,YAAY,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;QACrD,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,EAAE,CAAC,CAAC;QAC5B,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YAAE,OAAO,MAAM,CAAC;IAC9C,CAAC;IACD,gEAAgE;IAChE,MAAM,IAAI,GAAG,QAAQ,CAAC,UAAU,CAAC,CAAC;IAClC,OAAO,IAAI,CAAC,SAAS,CAAC;AACxB,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Search engine for WhySpec.
|
|
3
|
+
* Ported from GitWhy Go: internal/storage/tree.go
|
|
4
|
+
* - splitMarkdownSections (tree.go:786-822)
|
|
5
|
+
* - computeSearchScore (tree.go:736-781)
|
|
6
|
+
* - SearchContexts (tree.go:648-732)
|
|
7
|
+
*
|
|
8
|
+
* Searches all .md files in .gitwhy/changes/ (ctx_*.md, intent.md, design.md)
|
|
9
|
+
* using weighted section scoring.
|
|
10
|
+
*/
|
|
11
|
+
export interface SearchResult {
|
|
12
|
+
id: string;
|
|
13
|
+
title: string;
|
|
14
|
+
change_name: string;
|
|
15
|
+
domain: string;
|
|
16
|
+
score: number;
|
|
17
|
+
matched_sections: string[];
|
|
18
|
+
path: string;
|
|
19
|
+
snippet: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Split a markdown document into named sections.
|
|
23
|
+
* Returns a map keyed by lowercase section name (H2 headers).
|
|
24
|
+
* The special key "title" holds the first H1 line text.
|
|
25
|
+
*
|
|
26
|
+
* Ported from tree.go:splitMarkdownSections (lines 786-822).
|
|
27
|
+
*/
|
|
28
|
+
export declare function splitMarkdownSections(content: string): Record<string, string>;
|
|
29
|
+
/**
|
|
30
|
+
* Compute a relevance score for a document based on which markdown sections
|
|
31
|
+
* contain the query string (case-insensitive).
|
|
32
|
+
*
|
|
33
|
+
* Ported from tree.go:computeSearchScore (lines 736-781).
|
|
34
|
+
* Weights: title=100, prompt=50, reasoning=30, "what was done"=30, files=20, fallback=10.
|
|
35
|
+
*/
|
|
36
|
+
export declare function computeSearchScore(content: string, lowerQuery: string): {
|
|
37
|
+
score: number;
|
|
38
|
+
matchedSections: string[];
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Search all .md files in .gitwhy/changes/ for a query string.
|
|
42
|
+
* Returns scored results sorted by score descending, then date descending.
|
|
43
|
+
*
|
|
44
|
+
* Searches ctx_*.md, intent.md, and design.md (FR-25).
|
|
45
|
+
* Supports domain filtering and result limit.
|
|
46
|
+
*/
|
|
47
|
+
export declare function searchChanges(repoRoot: string, query: string, options?: {
|
|
48
|
+
domain?: string;
|
|
49
|
+
limit?: number;
|
|
50
|
+
}): SearchResult[];
|
|
51
|
+
//# sourceMappingURL=search.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/core/search.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAOH,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAoC7E;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,GACjB;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,EAAE,CAAA;CAAE,CA0C9C;AAiFD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAC5C,YAAY,EAAE,CAsEhB"}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Search engine for WhySpec.
|
|
3
|
+
* Ported from GitWhy Go: internal/storage/tree.go
|
|
4
|
+
* - splitMarkdownSections (tree.go:786-822)
|
|
5
|
+
* - computeSearchScore (tree.go:736-781)
|
|
6
|
+
* - SearchContexts (tree.go:648-732)
|
|
7
|
+
*
|
|
8
|
+
* Searches all .md files in .gitwhy/changes/ (ctx_*.md, intent.md, design.md)
|
|
9
|
+
* using weighted section scoring.
|
|
10
|
+
*/
|
|
11
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
12
|
+
import { basename, join, relative } from "node:path";
|
|
13
|
+
import { changesDir } from "./storage.js";
|
|
14
|
+
import { autoCategorize } from "./categorize.js";
|
|
15
|
+
/**
|
|
16
|
+
* Split a markdown document into named sections.
|
|
17
|
+
* Returns a map keyed by lowercase section name (H2 headers).
|
|
18
|
+
* The special key "title" holds the first H1 line text.
|
|
19
|
+
*
|
|
20
|
+
* Ported from tree.go:splitMarkdownSections (lines 786-822).
|
|
21
|
+
*/
|
|
22
|
+
export function splitMarkdownSections(content) {
|
|
23
|
+
const sections = {};
|
|
24
|
+
const lines = content.split("\n");
|
|
25
|
+
let currentSection = "";
|
|
26
|
+
let sectionLines = [];
|
|
27
|
+
for (const line of lines) {
|
|
28
|
+
// Capture the title (first H1).
|
|
29
|
+
if (!("title" in sections) && line.startsWith("# ")) {
|
|
30
|
+
sections["title"] = line.slice(2);
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
// Detect H2 section headers.
|
|
34
|
+
if (line.startsWith("## ")) {
|
|
35
|
+
// Flush the previous section.
|
|
36
|
+
if (currentSection) {
|
|
37
|
+
sections[currentSection] = sectionLines.join("\n");
|
|
38
|
+
}
|
|
39
|
+
currentSection = line.slice(3).toLowerCase();
|
|
40
|
+
sectionLines = [];
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (currentSection) {
|
|
44
|
+
sectionLines.push(line);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// Flush the last section.
|
|
48
|
+
if (currentSection) {
|
|
49
|
+
sections[currentSection] = sectionLines.join("\n");
|
|
50
|
+
}
|
|
51
|
+
return sections;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Compute a relevance score for a document based on which markdown sections
|
|
55
|
+
* contain the query string (case-insensitive).
|
|
56
|
+
*
|
|
57
|
+
* Ported from tree.go:computeSearchScore (lines 736-781).
|
|
58
|
+
* Weights: title=100, prompt=50, reasoning=30, "what was done"=30, files=20, fallback=10.
|
|
59
|
+
*/
|
|
60
|
+
export function computeSearchScore(content, lowerQuery) {
|
|
61
|
+
const sections = splitMarkdownSections(content);
|
|
62
|
+
let score = 0;
|
|
63
|
+
const matchedSections = [];
|
|
64
|
+
// Title match: first # line (100 points).
|
|
65
|
+
if (sections["title"] && sections["title"].toLowerCase().includes(lowerQuery)) {
|
|
66
|
+
score += 100;
|
|
67
|
+
matchedSections.push("title");
|
|
68
|
+
}
|
|
69
|
+
// Prompt section (50 points).
|
|
70
|
+
if (sections["prompt"] && sections["prompt"].toLowerCase().includes(lowerQuery)) {
|
|
71
|
+
score += 50;
|
|
72
|
+
matchedSections.push("prompt");
|
|
73
|
+
}
|
|
74
|
+
// Reasoning section (30 points).
|
|
75
|
+
if (sections["reasoning"] && sections["reasoning"].toLowerCase().includes(lowerQuery)) {
|
|
76
|
+
score += 30;
|
|
77
|
+
matchedSections.push("reasoning");
|
|
78
|
+
}
|
|
79
|
+
// What Was Done section (30 points).
|
|
80
|
+
if (sections["what was done"] && sections["what was done"].toLowerCase().includes(lowerQuery)) {
|
|
81
|
+
score += 30;
|
|
82
|
+
matchedSections.push("what was done");
|
|
83
|
+
}
|
|
84
|
+
// Files section (20 points).
|
|
85
|
+
if (sections["files"] && sections["files"].toLowerCase().includes(lowerQuery)) {
|
|
86
|
+
score += 20;
|
|
87
|
+
matchedSections.push("files");
|
|
88
|
+
}
|
|
89
|
+
// Fallback: if no specific section matched but the query IS in the content, give 10 points.
|
|
90
|
+
if (score === 0 && content.toLowerCase().includes(lowerQuery)) {
|
|
91
|
+
score += 10;
|
|
92
|
+
matchedSections.push("content");
|
|
93
|
+
}
|
|
94
|
+
return { score, matchedSections };
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Walk a directory recursively and collect all .md file paths.
|
|
98
|
+
*/
|
|
99
|
+
function walkMarkdownFiles(dir) {
|
|
100
|
+
const results = [];
|
|
101
|
+
if (!existsSync(dir))
|
|
102
|
+
return results;
|
|
103
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
104
|
+
for (const entry of entries) {
|
|
105
|
+
const fullPath = join(dir, entry.name);
|
|
106
|
+
if (entry.isDirectory()) {
|
|
107
|
+
results.push(...walkMarkdownFiles(fullPath));
|
|
108
|
+
}
|
|
109
|
+
else if (entry.name.startsWith("ctx_") && entry.name.endsWith(".md") ||
|
|
110
|
+
entry.name === "intent.md" ||
|
|
111
|
+
entry.name === "design.md") {
|
|
112
|
+
results.push(fullPath);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return results;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Extract a snippet from the highest-scoring section content.
|
|
119
|
+
*/
|
|
120
|
+
function extractSnippet(content, lowerQuery, maxLen = 150) {
|
|
121
|
+
const lower = content.toLowerCase();
|
|
122
|
+
const idx = lower.indexOf(lowerQuery);
|
|
123
|
+
if (idx === -1)
|
|
124
|
+
return content.slice(0, maxLen).trim();
|
|
125
|
+
// Show context around the match.
|
|
126
|
+
const start = Math.max(0, idx - 40);
|
|
127
|
+
const end = Math.min(content.length, idx + lowerQuery.length + maxLen - 40);
|
|
128
|
+
let snippet = content.slice(start, end).replace(/\n/g, " ").trim();
|
|
129
|
+
if (start > 0)
|
|
130
|
+
snippet = "..." + snippet;
|
|
131
|
+
if (end < content.length)
|
|
132
|
+
snippet = snippet + "...";
|
|
133
|
+
return snippet;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Extract the title from markdown content (first H1 line).
|
|
137
|
+
*/
|
|
138
|
+
function extractTitle(content) {
|
|
139
|
+
const match = content.match(/^# (.+)/m);
|
|
140
|
+
return match ? match[1].trim() : "";
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Extract file paths listed in a <files> tag or ## Files section.
|
|
144
|
+
*/
|
|
145
|
+
function extractFilePaths(content) {
|
|
146
|
+
const paths = [];
|
|
147
|
+
// Try XML <files> tag first.
|
|
148
|
+
const filesMatch = content.match(/<files>([\s\S]*?)<\/files>/);
|
|
149
|
+
if (filesMatch) {
|
|
150
|
+
const lines = filesMatch[1].split("\n").map((l) => l.trim()).filter(Boolean);
|
|
151
|
+
for (const line of lines) {
|
|
152
|
+
// Format: path/to/file — action — description
|
|
153
|
+
const parts = line.split(/\s+—\s+/);
|
|
154
|
+
if (parts[0])
|
|
155
|
+
paths.push(parts[0].trim());
|
|
156
|
+
}
|
|
157
|
+
return paths;
|
|
158
|
+
}
|
|
159
|
+
// Try ## Files section.
|
|
160
|
+
const sections = splitMarkdownSections(content);
|
|
161
|
+
if (sections["files"]) {
|
|
162
|
+
const lines = sections["files"].split("\n").map((l) => l.trim()).filter(Boolean);
|
|
163
|
+
for (const line of lines) {
|
|
164
|
+
const parts = line.split(/\s+—\s+/);
|
|
165
|
+
if (parts[0])
|
|
166
|
+
paths.push(parts[0].trim());
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return paths;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Search all .md files in .gitwhy/changes/ for a query string.
|
|
173
|
+
* Returns scored results sorted by score descending, then date descending.
|
|
174
|
+
*
|
|
175
|
+
* Searches ctx_*.md, intent.md, and design.md (FR-25).
|
|
176
|
+
* Supports domain filtering and result limit.
|
|
177
|
+
*/
|
|
178
|
+
export function searchChanges(repoRoot, query, options) {
|
|
179
|
+
if (!query)
|
|
180
|
+
return [];
|
|
181
|
+
const dir = changesDir(repoRoot);
|
|
182
|
+
if (!existsSync(dir))
|
|
183
|
+
return [];
|
|
184
|
+
const lowerQuery = query.toLowerCase();
|
|
185
|
+
const limit = options?.limit ?? 10;
|
|
186
|
+
const results = [];
|
|
187
|
+
// Walk all change directories.
|
|
188
|
+
const changeDirs = readdirSync(dir, { withFileTypes: true })
|
|
189
|
+
.filter((d) => d.isDirectory())
|
|
190
|
+
.map((d) => d.name);
|
|
191
|
+
for (const changeName of changeDirs) {
|
|
192
|
+
const changeDir = join(dir, changeName);
|
|
193
|
+
const mdFiles = walkMarkdownFiles(changeDir);
|
|
194
|
+
for (const filePath of mdFiles) {
|
|
195
|
+
const content = readFileSync(filePath, "utf-8");
|
|
196
|
+
const lowerContent = content.toLowerCase();
|
|
197
|
+
// Only proceed if the query matches somewhere in the file.
|
|
198
|
+
if (!lowerContent.includes(lowerQuery))
|
|
199
|
+
continue;
|
|
200
|
+
const { score, matchedSections } = computeSearchScore(content, lowerQuery);
|
|
201
|
+
const title = extractTitle(content) || changeName;
|
|
202
|
+
const filePaths = extractFilePaths(content);
|
|
203
|
+
const { domain } = autoCategorize(title, filePaths);
|
|
204
|
+
// Apply domain filter if specified.
|
|
205
|
+
if (options?.domain && domain !== options.domain)
|
|
206
|
+
continue;
|
|
207
|
+
// Derive ID from filename (without .md extension). Use path.basename for cross-platform safety.
|
|
208
|
+
const id = basename(filePath, ".md");
|
|
209
|
+
const snippet = extractSnippet(content, lowerQuery);
|
|
210
|
+
const relPath = relative(repoRoot, filePath);
|
|
211
|
+
const mtime = statSync(filePath).mtime;
|
|
212
|
+
results.push({
|
|
213
|
+
result: {
|
|
214
|
+
id,
|
|
215
|
+
title,
|
|
216
|
+
change_name: changeName,
|
|
217
|
+
domain,
|
|
218
|
+
score,
|
|
219
|
+
matched_sections: matchedSections,
|
|
220
|
+
path: relPath,
|
|
221
|
+
snippet,
|
|
222
|
+
},
|
|
223
|
+
mtime,
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
// Sort by score descending, then by date descending for ties.
|
|
228
|
+
results.sort((a, b) => {
|
|
229
|
+
if (a.result.score !== b.result.score)
|
|
230
|
+
return b.result.score - a.result.score;
|
|
231
|
+
return b.mtime.getTime() - a.mtime.getTime();
|
|
232
|
+
});
|
|
233
|
+
return results.slice(0, limit).map((r) => r.result);
|
|
234
|
+
}
|
|
235
|
+
//# sourceMappingURL=search.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.js","sourceRoot":"","sources":["../../src/core/search.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storage module for WhySpec.
|
|
3
|
+
* Manages .gitwhy/ directory structure, reads/writes markdown files,
|
|
4
|
+
* generates context IDs.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Generates a context ID: ctx_ + 8-char alphanumeric.
|
|
8
|
+
* Uses crypto.randomBytes for randomness.
|
|
9
|
+
*/
|
|
10
|
+
export declare function generateContextId(): string;
|
|
11
|
+
/** Returns the .gitwhy directory path for a repo root. */
|
|
12
|
+
export declare function gitwhyDir(repoRoot: string): string;
|
|
13
|
+
/** Returns the changes directory path. */
|
|
14
|
+
export declare function changesDir(repoRoot: string): string;
|
|
15
|
+
/**
|
|
16
|
+
* Ensures the .gitwhy/ directory structure exists:
|
|
17
|
+
* .gitwhy/
|
|
18
|
+
* ├── changes/
|
|
19
|
+
* ├── archive/
|
|
20
|
+
* └── debug/
|
|
21
|
+
*/
|
|
22
|
+
export declare function ensureDirectoryStructure(repoRoot: string): void;
|
|
23
|
+
/**
|
|
24
|
+
* Creates a change directory under .gitwhy/changes/<name>/.
|
|
25
|
+
* Returns the created path.
|
|
26
|
+
*/
|
|
27
|
+
export declare function createChangeDir(repoRoot: string, changeName: string): string;
|
|
28
|
+
/** Reads a markdown file from a change directory. Returns null if not found. */
|
|
29
|
+
export declare function readMarkdown(changeDir: string, filename: string): string | null;
|
|
30
|
+
/** Writes a markdown file to a change directory. */
|
|
31
|
+
export declare function writeMarkdown(changeDir: string, filename: string, content: string): void;
|
|
32
|
+
/** Lists all change names in .gitwhy/changes/. */
|
|
33
|
+
export declare function listChanges(repoRoot: string): string[];
|
|
34
|
+
/** Checks if a specific change directory exists. */
|
|
35
|
+
export declare function changeExists(repoRoot: string, changeName: string): boolean;
|
|
36
|
+
//# sourceMappingURL=storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../src/core/storage.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAWH;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAG1C;AAED,0DAA0D;AAC1D,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAElD;AAED,0CAA0C;AAC1C,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAU/D;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAI5E;AAED,gFAAgF;AAChF,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAI/E;AAED,oDAAoD;AACpD,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAExF;AAED,kDAAkD;AAClD,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAMtD;AAED,oDAAoD;AACpD,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAE1E"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storage module for WhySpec.
|
|
3
|
+
* Manages .gitwhy/ directory structure, reads/writes markdown files,
|
|
4
|
+
* generates context IDs.
|
|
5
|
+
*/
|
|
6
|
+
import { mkdirSync, readFileSync, writeFileSync, existsSync, readdirSync } from "node:fs";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { randomBytes } from "node:crypto";
|
|
9
|
+
const GITWHY_DIR = ".gitwhy";
|
|
10
|
+
const CHANGES_DIR = "changes";
|
|
11
|
+
const ARCHIVE_DIR = "archive";
|
|
12
|
+
const DEBUG_DIR = "debug";
|
|
13
|
+
/**
|
|
14
|
+
* Generates a context ID: ctx_ + 8-char alphanumeric.
|
|
15
|
+
* Uses crypto.randomBytes for randomness.
|
|
16
|
+
*/
|
|
17
|
+
export function generateContextId() {
|
|
18
|
+
const bytes = randomBytes(4);
|
|
19
|
+
return `ctx_${bytes.toString("hex")}`;
|
|
20
|
+
}
|
|
21
|
+
/** Returns the .gitwhy directory path for a repo root. */
|
|
22
|
+
export function gitwhyDir(repoRoot) {
|
|
23
|
+
return join(repoRoot, GITWHY_DIR);
|
|
24
|
+
}
|
|
25
|
+
/** Returns the changes directory path. */
|
|
26
|
+
export function changesDir(repoRoot) {
|
|
27
|
+
return join(repoRoot, GITWHY_DIR, CHANGES_DIR);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Ensures the .gitwhy/ directory structure exists:
|
|
31
|
+
* .gitwhy/
|
|
32
|
+
* ├── changes/
|
|
33
|
+
* ├── archive/
|
|
34
|
+
* └── debug/
|
|
35
|
+
*/
|
|
36
|
+
export function ensureDirectoryStructure(repoRoot) {
|
|
37
|
+
const dirs = [
|
|
38
|
+
join(repoRoot, GITWHY_DIR),
|
|
39
|
+
join(repoRoot, GITWHY_DIR, CHANGES_DIR),
|
|
40
|
+
join(repoRoot, GITWHY_DIR, ARCHIVE_DIR),
|
|
41
|
+
join(repoRoot, GITWHY_DIR, DEBUG_DIR),
|
|
42
|
+
];
|
|
43
|
+
for (const dir of dirs) {
|
|
44
|
+
mkdirSync(dir, { recursive: true });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Creates a change directory under .gitwhy/changes/<name>/.
|
|
49
|
+
* Returns the created path.
|
|
50
|
+
*/
|
|
51
|
+
export function createChangeDir(repoRoot, changeName) {
|
|
52
|
+
const dir = join(repoRoot, GITWHY_DIR, CHANGES_DIR, changeName);
|
|
53
|
+
mkdirSync(dir, { recursive: true });
|
|
54
|
+
return dir;
|
|
55
|
+
}
|
|
56
|
+
/** Reads a markdown file from a change directory. Returns null if not found. */
|
|
57
|
+
export function readMarkdown(changeDir, filename) {
|
|
58
|
+
const path = join(changeDir, filename);
|
|
59
|
+
if (!existsSync(path))
|
|
60
|
+
return null;
|
|
61
|
+
return readFileSync(path, "utf-8");
|
|
62
|
+
}
|
|
63
|
+
/** Writes a markdown file to a change directory. */
|
|
64
|
+
export function writeMarkdown(changeDir, filename, content) {
|
|
65
|
+
writeFileSync(join(changeDir, filename), content, "utf-8");
|
|
66
|
+
}
|
|
67
|
+
/** Lists all change names in .gitwhy/changes/. */
|
|
68
|
+
export function listChanges(repoRoot) {
|
|
69
|
+
const dir = changesDir(repoRoot);
|
|
70
|
+
if (!existsSync(dir))
|
|
71
|
+
return [];
|
|
72
|
+
return readdirSync(dir, { withFileTypes: true })
|
|
73
|
+
.filter((d) => d.isDirectory())
|
|
74
|
+
.map((d) => d.name);
|
|
75
|
+
}
|
|
76
|
+
/** Checks if a specific change directory exists. */
|
|
77
|
+
export function changeExists(repoRoot, changeName) {
|
|
78
|
+
return existsSync(join(repoRoot, GITWHY_DIR, CHANGES_DIR, changeName));
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=storage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.js","sourceRoot":"","sources":["../../src/core/storage.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAC1F,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,MAAM,UAAU,GAAG,SAAS,CAAC;AAC7B,MAAM,WAAW,GAAG,SAAS,CAAC;AAC9B,MAAM,WAAW,GAAG,SAAS,CAAC;AAC9B,MAAM,SAAS,GAAG,OAAO,CAAC;AAE1B;;;GAGG;AACH,MAAM,UAAU,iBAAiB;IAC/B,MAAM,KAAK,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC;IAC7B,OAAO,OAAO,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;AACxC,CAAC;AAED,0DAA0D;AAC1D,MAAM,UAAU,SAAS,CAAC,QAAgB;IACxC,OAAO,IAAI,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;AACpC,CAAC;AAED,0CAA0C;AAC1C,MAAM,UAAU,UAAU,CAAC,QAAgB;IACzC,OAAO,IAAI,CAAC,QAAQ,EAAE,UAAU,EAAE,WAAW,CAAC,CAAC;AACjD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,wBAAwB,CAAC,QAAgB;IACvD,MAAM,IAAI,GAAG;QACX,IAAI,CAAC,QAAQ,EAAE,UAAU,CAAC;QAC1B,IAAI,CAAC,QAAQ,EAAE,UAAU,EAAE,WAAW,CAAC;QACvC,IAAI,CAAC,QAAQ,EAAE,UAAU,EAAE,WAAW,CAAC;QACvC,IAAI,CAAC,QAAQ,EAAE,UAAU,EAAE,SAAS,CAAC;KACtC,CAAC;IACF,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACtC,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,QAAgB,EAAE,UAAkB;IAClE,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,CAAC,CAAC;IAChE,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACpC,OAAO,GAAG,CAAC;AACb,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,YAAY,CAAC,SAAiB,EAAE,QAAgB;IAC9D,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;IACvC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACnC,OAAO,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;AACrC,CAAC;AAED,oDAAoD;AACpD,MAAM,UAAU,aAAa,CAAC,SAAiB,EAAE,QAAgB,EAAE,OAAe;IAChF,aAAa,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;AAC7D,CAAC;AAED,kDAAkD;AAClD,MAAM,UAAU,WAAW,CAAC,QAAgB;IAC1C,MAAM,GAAG,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;IACjC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,EAAE,CAAC;IAChC,OAAO,WAAW,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC;SAC7C,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;SAC9B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;AACxB,CAAC;AAED,oDAAoD;AACpD,MAAM,UAAU,YAAY,CAAC,QAAgB,EAAE,UAAkB;IAC/D,OAAO,UAAU,CAAC,IAAI,CAAC,QAAQ,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,CAAC,CAAC,CAAC;AACzE,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File templates for WhySpec change artifacts.
|
|
3
|
+
* Templates for: intent.md, design.md, tasks.md, context (SaaS XML format), debug.md
|
|
4
|
+
*
|
|
5
|
+
* The context template MUST match GitWhy SaaS XML format exactly.
|
|
6
|
+
* See: gitwhy/.claude/skills/gitwhy/SKILL.md
|
|
7
|
+
*/
|
|
8
|
+
/** Template for intent.md — declares intent before coding. */
|
|
9
|
+
export declare function intentTemplate(changeName: string): string;
|
|
10
|
+
/** Template for design.md — technical approach with trade-offs. */
|
|
11
|
+
export declare function designTemplate(changeName: string): string;
|
|
12
|
+
/** Template for tasks.md — implementation checklist with goal-backward verification. */
|
|
13
|
+
export declare function tasksTemplate(changeName: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* Context template in GitWhy SaaS XML format.
|
|
16
|
+
* This is what agents fill in when capturing reasoning after coding.
|
|
17
|
+
* The CLI parses this XML and renders it into rich markdown for storage.
|
|
18
|
+
*/
|
|
19
|
+
export declare function contextTemplate(options?: {
|
|
20
|
+
title?: string;
|
|
21
|
+
agent?: string;
|
|
22
|
+
tags?: string;
|
|
23
|
+
}): string;
|
|
24
|
+
/** Template for debug.md — structured debugging process. */
|
|
25
|
+
export declare function debugTemplate(bugName: string): string;
|
|
26
|
+
/** Returns a template by type name. */
|
|
27
|
+
export declare function getTemplate(type: "intent" | "design" | "tasks" | "context" | "debug", name?: string): string;
|
|
28
|
+
//# sourceMappingURL=templates.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../../src/core/templates.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,8DAA8D;AAC9D,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAsBzD;AAED,mEAAmE;AACnE,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CA6BzD;AAED,wFAAwF;AACxF,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAWxD;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,OAAO,CAAC,EAAE;IACxC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,GAAG,MAAM,CAuCT;AAED,4DAA4D;AAC5D,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAuCrD;AAED,uCAAuC;AACvC,wBAAgB,WAAW,CACzB,IAAI,EAAE,QAAQ,GAAG,QAAQ,GAAG,OAAO,GAAG,SAAS,GAAG,OAAO,EACzD,IAAI,SAAK,GACR,MAAM,CAaR"}
|