From 0a17785feb9fd5c9500aa6fc9029957f6c97eee5 Mon Sep 17 00:00:00 2001 From: Ben Colborn Date: Thu, 13 Mar 2025 13:02:02 -0700 Subject: [PATCH 01/21] add instructions for local instance --- README.md | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 1c69a64e..6555691d 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,17 @@ # DevRev Docs -Tagging a release on this repository will update the following clients: +Merging a change in this repository will update the following clients: -- [API Docs](https://devrev.docs.buildwithfern.com) - -## What is in this repository? +- [API Docs](https://developer.devrev.ai) This repository contains - DevRev's `Public` OpenAPI spec & `Beta` OpenAPI spec - Fern configuration -## What is in the API Definition? +## API definition -The API Definition contains an OpenAPI specification adapted to be compatible with Fern. +The API Definition contains an OpenAPI specification adapted to be compatible with Fern. The specs are in `/fern/apis`. To make sure that the definition is valid, you can use the Fern CLI. @@ -22,9 +20,9 @@ npm install -g fern-api # Installs CLI fern check # Checks if the definition is valid ``` -## What are generators? +## Generators -Generators read in your API Definition and output artifacts (e.g. the TypeScript SDK Generator) and are tracked in [generators.yml](./fern/api/generators.yml). +Generators read in your API Definition and output artifacts (the TypeScript SDK Generator) and are tracked in [generators.yml](./fern/api/generators.yml). To trigger the generators run: @@ -33,15 +31,14 @@ To trigger the generators run: fern generate --version ``` -## Upgrading Fern -You can use the following command to upgrade fern to the latest version: -```bash -fern upgrade --rc +## Run a local instance + +In the root of this repository: +``` +cd custom-implementation/ && npm i && npm run build && cd .. +fern docs dev ``` ### Troubleshooting If you run into errors, you can add the ` --log-level debug` flag to get more information. - -If you get a 403 error, you may need to first run `fern login`. You might also need to be added -to the Vellum org in Fern, which requires contacting the fern team in #fern-x-vellum in Slack. From 9dffc202fb4f0b5bc603e6b3ca7db8c3ca772856 Mon Sep 17 00:00:00 2001 From: Ben Colborn Date: Tue, 18 Mar 2025 16:10:27 -0700 Subject: [PATCH 02/21] generate prompt --- stylecheck.py | 41 +++++++++++++++++++++++++++++++++ stylecheck/prompt.md | 5 ++++ stylecheck/sample.mdx | 47 ++++++++++++++++++++++++++++++++++++++ stylecheck/style-devrev.md | 25 ++++++++++++++++++++ 4 files changed, 118 insertions(+) create mode 100644 stylecheck.py create mode 100644 stylecheck/prompt.md create mode 100644 stylecheck/sample.mdx create mode 100644 stylecheck/style-devrev.md diff --git a/stylecheck.py b/stylecheck.py new file mode 100644 index 00000000..d7be09de --- /dev/null +++ b/stylecheck.py @@ -0,0 +1,41 @@ +import yaml +import argparse +import requests +import os +import pathlib +import re + + +def main(args): + print(f"Checking style for {args.doc}") + with open(args.doc, 'r', encoding="utf-8") as infile: + content = infile.read() + + style = 'stylecheck/style-devrev.md' + with open(style, 'r', encoding="utf-8") as infile: + style = infile.read() + + prompt = gen_prompt(content, style) + prompt_file = f"temp/prompt.md" + with open(prompt_file, 'w', encoding="utf-8") as outfile: + outfile.write(prompt) + print(f"Wrote prompt to {prompt_file}.") + + +def gen_prompt(content, style): + prompt = 'stylecheck/prompt.md' + with open(prompt, 'r') as infile: + prompt = infile.read() + prompt += "\n\n" + prompt += style + prompt += "\n\n\n\n" + prompt += content + prompt += "\n\n\n\n" + return prompt + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Check writing style of markdown file") + parser.add_argument('--doc', type=pathlib.Path, required=True) + args = parser.parse_args() + main(args) \ No newline at end of file diff --git a/stylecheck/prompt.md b/stylecheck/prompt.md new file mode 100644 index 00000000..a5dac42f --- /dev/null +++ b/stylecheck/prompt.md @@ -0,0 +1,5 @@ +## Prompt + +You are an expert technical documentation editor. Analyze the included document and revise it so that it adheres to the following style rules. In your response, summarize the violations that you fixed. Return the revised markdown in a separate message element that contains only the document and no other commentary. I’m going to read your suggestions through an API and need to be able to get just the document. + +While you can make suggestions for being more concise, be very careful not to remove any facts from the document. diff --git a/stylecheck/sample.mdx b/stylecheck/sample.mdx new file mode 100644 index 00000000..233e079b --- /dev/null +++ b/stylecheck/sample.mdx @@ -0,0 +1,47 @@ +# DataDog Snap-in + +This integration links Datadog's monitoring system with DevRev's incident management, simplifying the process of managing incidents triggered by Datadog alerts. It automatically generates new incidents and updates existing incidents in DevRev using data from Datadog, allowing your team to address issues efficiently without manual tracking. + +### System Features + +- Automated Incident Creation: When an incident is triggered in Datadog, the snap-in automatically creates a corresponding incident in DevRev. +- Automated Incident Updation: When an incident is updated in Datadog, the changes are reflected in DevRev. +- Real-Time Synchronization: The snap-in ensures that incidents are created or updated in real-time, reducing delays between detection and resolution. DevRev captures the necessary incident details from the Datadog payload, allowing teams to address and mitigate issues. +- Seamless integration: The snap-in is built to streamline workflows, minimizing the need for manual intervention. + +### Installing the Snap-in + +1. Open the DevRev marketplace and install the **Datadog** snap-in. +2. Select the dev org where you want to install the snap-in, confirm your +selection, and click **Deploy snap-in**. +3. In DevRev app, setup the connection in **Settings** > **Snap-ins** > +**Connections** on top. + - Search and choose an existing connection or create a new one by clicking + **+ Connection**. + - Select **Datadog** from the dropdown list. + - Give it a connection name and paste your Datadog **API Key**, **Application + Key** and **Environment**(prod/dev) in their respective fields. + +### Configuration + +1. Navigate to **Snap-ins** > **All Snap-ins** > **Datadog** > **Configure**. +2. Select the connection that you created in the **Connections** tab. + + This connection is necessary if you wish to bring stage and custom fields to DevRev. + +3. The default owner, part, and default severity value for incidents must be included. +4. Include the desired stage mapping from Datadog to DevRev. +5. Click **Save** +6. Click **Install**. +7. Copy the webhook URL and follow the below steps to connect it to Datadog via webhook integration. + 1. Add the following payload in the **Payload** section. + - `"aggreg_key": "$AGGREG_KEY"`. + - `"alert_metric": "$ALERT_METRIC"`. + - `"alert_query": "$ALERT_QUERY"`. + 2. Click **Install Integration** or **Update Configuration**. + +### References + +- [DataDog DevRev snap-in marketplace listing](https://marketplace.devrev.ai/datadog-snapin) +- [DataDog DevRev snapin documentation](https://devrev.ai/docs/integrations/datadog) +- [DataDog API](https://docs.datadoghq.com/api/latest/) \ No newline at end of file diff --git a/stylecheck/style-devrev.md b/stylecheck/style-devrev.md new file mode 100644 index 00000000..9c5e2059 --- /dev/null +++ b/stylecheck/style-devrev.md @@ -0,0 +1,25 @@ +## Rules + +For the writing style, apply the following descriptive keywords to analyzing the draft document: + +- Writing style: authoritative, comprehensive, helpful +- Sentence structure: varied, complex, cohesive +- Vocabulary choice: consistent, technical +- Grammar and syntax: structured, flawless, articulate +- Descriptive language: clear, concise, informative + +There are also some very specific style rules that need to be followed: + +- Use sentence case (only capitalize the first word and proper nouns) in any type of heading, including in two-level lists. +- Ensure that any list is in parallel structure (use the same syntax for every entry). +- Instructions or how-to guides: + - They may use two levels; both should be ordered (numbered) lists, not unordered (bulleted). + - Instructions should be in imperative mood. + - If there is a location or condition in a step, it should be at the front of the sentence, which must still be in imperative. + - Steps should be more than a single “click”; combine steps if needed to make them more meaningful. +- End each list item with a period or other appropriate sentence-ending punctuation, except in the following cases: + - If the item consists of a single word, don't add end punctuation. + - If the item doesn't include a verb, don't add end punctuation. + - If the item is entirely in code font, don't add end punctuation. + - If the item is entirely link text or a document title, don't add end punctuation. +- Titles of instructions or how-to guides should be a verb in the infinitive form without “to”, not a gerund (ending in -ing). Titles of any other type of section should be noun phrases. \ No newline at end of file From 862f8e193950df8ab1edff6a0dff37e8f061ab4b Mon Sep 17 00:00:00 2001 From: Ben Colborn Date: Tue, 18 Mar 2025 18:22:12 -0700 Subject: [PATCH 03/21] refactored for reuse and graceful failure --- .gitignore | 3 ++- changelog.py | 45 ++++++++------------------------------------- llm_client.py | 31 +++++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 38 deletions(-) create mode 100644 llm_client.py diff --git a/.gitignore b/.gitignore index 91a128ca..4557c521 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ fern/dist fern/*/definition/ .DS_Store .idea -temp \ No newline at end of file +temp +__pycache__ \ No newline at end of file diff --git a/changelog.py b/changelog.py index fdd41f1e..74ee1344 100644 --- a/changelog.py +++ b/changelog.py @@ -1,9 +1,7 @@ import yaml import argparse -import requests -import os import datetime -import re +import llm_client def main(vrn, d): @@ -17,43 +15,16 @@ def main(vrn, d): outfile.write(p) print(f"Wrote prompt to {pr_file}.") - l = gen_log(p) + l = llm_client.get_response(p) log_file = f"./fern/apis/{vrn}/changelog/{d}.md" - with open(log_file, 'w', encoding="utf-8") as outfile: - outfile.write(l) - print(f"Wrote log to {log_file}.") - - -def gen_log(prompt): - - auth = os.environ.get('LLM_JWT') - if auth: - headers = {"Content-Type": "application/json", "Authorization": f"Bearer {auth}"} - payload = { - "model": "us.anthropic.claude-3-5-sonnet-20241022-v2:0", - "messages": [ - { - "role": "user", - "content": prompt - } - ] - } - - try: - r = requests.post('https://openwebui.dev.devrev-eng.ai/api/chat/completions', json=payload, - headers=headers) - log = r.json()['choices'][0]['message']['content'] - log = re.sub(r"^# .*\n?", '', log, flags=re.MULTILINE) - log = re.sub(r"^Here.*\n?", '', log, flags=re.MULTILINE) - log = re.sub(r"^Let me know.*\n?", '', log, flags=re.MULTILINE) - except Exception as e: - print( - f"Failed to generate changelog. Error: {type(e)} {e} {r}") + if (l): + with open(log_file, 'w', encoding="utf-8") as outfile: + outfile.write(l) + print(f"Wrote log to {log_file}.") else: - log = "No auth token" - - return log + print(f"Failed to generate {log_file}.") + def gen_prompt(oasdiff, links, version): diff --git a/llm_client.py b/llm_client.py new file mode 100644 index 00000000..a9901901 --- /dev/null +++ b/llm_client.py @@ -0,0 +1,31 @@ +import os +import re +import requests + +def get_response(prompt): + + auth = os.environ.get('LLM_JWT') + if auth: + headers = {"Content-Type": "application/json", "Authorization": f"Bearer {auth}"} + payload = { + "model": "us.anthropic.claude-3-5-sonnet-20241022-v2:0", + "messages": [ + { + "role": "user", + "content": prompt + } + ] + } + + try: + r = requests.post('https://openwebui.dev.devrev-eng.ai/api/chat/completions', json=payload, + headers=headers) + response = r.json()['choices'][0]['message']['content'] + response = re.sub(r"^# .*\n?", '', response, flags=re.MULTILINE) + response = re.sub(r"^Here.*\n?", '', response, flags=re.MULTILINE) + response = re.sub(r"^Let me know.*\n?", '', response, flags=re.MULTILINE) + return response + except Exception as e: + print(f"Failed to generate changelog. Error: {type(e)} {e} {r}") + return None + \ No newline at end of file From 50950a068911d552d7cbc1d7907fc5f05e7f7895 Mon Sep 17 00:00:00 2001 From: Ben Colborn Date: Tue, 18 Mar 2025 20:15:04 -0700 Subject: [PATCH 04/21] send to LLM --- stylecheck.py | 17 ++++++++++++----- stylecheck/prompt.md | 4 ++-- stylecheck/{style-devrev.md => style-common.md} | 0 3 files changed, 14 insertions(+), 7 deletions(-) rename stylecheck/{style-devrev.md => style-common.md} (100%) diff --git a/stylecheck.py b/stylecheck.py index d7be09de..472fe54e 100644 --- a/stylecheck.py +++ b/stylecheck.py @@ -1,17 +1,14 @@ -import yaml import argparse -import requests import os import pathlib -import re - +import llm_client def main(args): print(f"Checking style for {args.doc}") with open(args.doc, 'r', encoding="utf-8") as infile: content = infile.read() - style = 'stylecheck/style-devrev.md' + style = 'stylecheck/style-common.md' with open(style, 'r', encoding="utf-8") as infile: style = infile.read() @@ -21,6 +18,16 @@ def main(args): outfile.write(prompt) print(f"Wrote prompt to {prompt_file}.") + revised = llm_client.get_response(prompt) + + revision_file = "/".join(['temp', os.path.basename(args.doc)]) + if (revised): + with open(revision_file, 'w', encoding="utf-8") as outfile: + outfile.write(revised) + print(f"Wrote log to {revision_file}.") + else: + print(f"Failed to generate {revision_file}.") + def gen_prompt(content, style): prompt = 'stylecheck/prompt.md' diff --git a/stylecheck/prompt.md b/stylecheck/prompt.md index a5dac42f..5d5dc542 100644 --- a/stylecheck/prompt.md +++ b/stylecheck/prompt.md @@ -1,5 +1,5 @@ ## Prompt -You are an expert technical documentation editor. Analyze the included document and revise it so that it adheres to the following style rules. In your response, summarize the violations that you fixed. Return the revised markdown in a separate message element that contains only the document and no other commentary. I’m going to read your suggestions through an API and need to be able to get just the document. +You are an expert technical documentation editor. Analyze the included document and revise it so that it adheres to the following style rules. In your response, summarize the violations that you fixed. Return the revised markdown inside a separate element `` element, just like in this request, that contains only the document and no other commentary. I’m going to read your suggestions through an API and need to be able to get just the document for the next stage. -While you can make suggestions for being more concise, be very careful not to remove any facts from the document. +While you can make suggestions for being more concise, be very careful not to remove any facts from the document. Also do not change the document structure, including removing any headings. diff --git a/stylecheck/style-devrev.md b/stylecheck/style-common.md similarity index 100% rename from stylecheck/style-devrev.md rename to stylecheck/style-common.md From 978a68bc883d15b1fbafafca027fa27e36330b3f Mon Sep 17 00:00:00 2001 From: Ben Colborn Date: Wed, 19 Mar 2025 08:37:38 -0700 Subject: [PATCH 05/21] extract revision from element --- llm_client.py | 6 +++++- stylecheck.py | 16 ++++++++++++---- stylecheck/prompt.md | 2 +- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/llm_client.py b/llm_client.py index a9901901..722edde8 100644 --- a/llm_client.py +++ b/llm_client.py @@ -28,4 +28,8 @@ def get_response(prompt): except Exception as e: print(f"Failed to generate changelog. Error: {type(e)} {e} {r}") return None - \ No newline at end of file + +def get_lines_between_tags(text, tag): + pattern = r'<' + tag + r'>(.*?)<\/' + tag + r'>' + matches = re.findall(pattern, text, re.DOTALL) + return "".join([match.strip() for match in matches]) \ No newline at end of file diff --git a/stylecheck.py b/stylecheck.py index 472fe54e..71f6fc21 100644 --- a/stylecheck.py +++ b/stylecheck.py @@ -18,13 +18,21 @@ def main(args): outfile.write(prompt) print(f"Wrote prompt to {prompt_file}.") - revised = llm_client.get_response(prompt) + response = llm_client.get_response(prompt) + response_file = "/".join(['temp', 'full_' + os.path.basename(args.doc)]) + if (response): + with open(response_file, 'w', encoding="utf-8") as outfile: + outfile.write(response) + print(f"Wrote response to {response_file}.") + else: + print(f"Failed to generate {response_file}.") + revision = llm_client.get_lines_between_tags(response, 'document') revision_file = "/".join(['temp', os.path.basename(args.doc)]) - if (revised): + if (revision): with open(revision_file, 'w', encoding="utf-8") as outfile: - outfile.write(revised) - print(f"Wrote log to {revision_file}.") + outfile.write(revision) + print(f"Wrote revision to {revision_file}.") else: print(f"Failed to generate {revision_file}.") diff --git a/stylecheck/prompt.md b/stylecheck/prompt.md index 5d5dc542..81e165f1 100644 --- a/stylecheck/prompt.md +++ b/stylecheck/prompt.md @@ -2,4 +2,4 @@ You are an expert technical documentation editor. Analyze the included document and revise it so that it adheres to the following style rules. In your response, summarize the violations that you fixed. Return the revised markdown inside a separate element `` element, just like in this request, that contains only the document and no other commentary. I’m going to read your suggestions through an API and need to be able to get just the document for the next stage. -While you can make suggestions for being more concise, be very careful not to remove any facts from the document. Also do not change the document structure, including removing any headings. +While you can make suggestions for being more concise, be very careful not to remove any facts from the document. Also do not change the document structure, including removing the title or any headings. From b639b584ae84b109dab7ee97429430eb34990ae4 Mon Sep 17 00:00:00 2001 From: Ben Colborn Date: Wed, 19 Mar 2025 17:40:45 -0700 Subject: [PATCH 06/21] specify additional styles --- stylecheck.py | 8 ++++ stylecheck/prompt.md | 4 +- stylecheck/{sample.mdx => sample-common.mdx} | 0 stylecheck/sample-developer.mdx | 39 ++++++++++++++++++++ stylecheck/style-developer.md | 17 +++++++++ 5 files changed, 66 insertions(+), 2 deletions(-) rename stylecheck/{sample.mdx => sample-common.mdx} (100%) create mode 100644 stylecheck/sample-developer.mdx create mode 100644 stylecheck/style-developer.md diff --git a/stylecheck.py b/stylecheck.py index 71f6fc21..d9466279 100644 --- a/stylecheck.py +++ b/stylecheck.py @@ -12,6 +12,12 @@ def main(args): with open(style, 'r', encoding="utf-8") as infile: style = infile.read() + if args.style and os.path.exists(args.style): + with open(args.style, 'r', encoding="utf-8") as infile: + style += "\n\n" + style += infile.read() + style += "\n\n" + prompt = gen_prompt(content, style) prompt_file = f"temp/prompt.md" with open(prompt_file, 'w', encoding="utf-8") as outfile: @@ -52,5 +58,7 @@ def gen_prompt(content, style): if __name__ == "__main__": parser = argparse.ArgumentParser(description="Check writing style of markdown file") parser.add_argument('--doc', type=pathlib.Path, required=True) + parser.add_argument('--style', type=pathlib.Path) + args = parser.parse_args() main(args) \ No newline at end of file diff --git a/stylecheck/prompt.md b/stylecheck/prompt.md index 81e165f1..3f35d05c 100644 --- a/stylecheck/prompt.md +++ b/stylecheck/prompt.md @@ -1,5 +1,5 @@ ## Prompt -You are an expert technical documentation editor. Analyze the included document and revise it so that it adheres to the following style rules. In your response, summarize the violations that you fixed. Return the revised markdown inside a separate element `` element, just like in this request, that contains only the document and no other commentary. I’m going to read your suggestions through an API and need to be able to get just the document for the next stage. +You are an expert technical documentation editor. Analyze the included document and revise it so that it adheres to the following style rules. While you can make suggestions for being more concise, be very careful not to remove any facts from the document. Also do not change the document structure, including removing the title or any headings, unless the original structure violates any of the guidelines. -While you can make suggestions for being more concise, be very careful not to remove any facts from the document. Also do not change the document structure, including removing the title or any headings. +In your response, summarize the violations that you fixed. Return the revised markdown inside a separate element `` element, just like in this request, that contains only the document and no other commentary. I’m going to read your suggestions through an API and need to be able to get just the document for the next stage. \ No newline at end of file diff --git a/stylecheck/sample.mdx b/stylecheck/sample-common.mdx similarity index 100% rename from stylecheck/sample.mdx rename to stylecheck/sample-common.mdx diff --git a/stylecheck/sample-developer.mdx b/stylecheck/sample-developer.mdx new file mode 100644 index 00000000..92490c83 --- /dev/null +++ b/stylecheck/sample-developer.mdx @@ -0,0 +1,39 @@ +# Developer sample + +## Initialize + +Place the following code in the `` section of your HTML page. + +```html + +``` +```jsx +; +``` +```jsx +useEffect(() => { + window.plugSDK.init({ + // Please ensure you replace the app_id with your unique app id + app_id: "", + }); +}, []); +``` + +## Tracking user events + +To track user events through PLuG, employ the `trackEvent` method provided by the PLuG SDK. + +**trackEvent** + +``` +window.plugSDK.trackEvent(event_name, properties) + +``` \ No newline at end of file diff --git a/stylecheck/style-developer.md b/stylecheck/style-developer.md new file mode 100644 index 00000000..32475c94 --- /dev/null +++ b/stylecheck/style-developer.md @@ -0,0 +1,17 @@ +## Developer-specific style + +- Always specify a language for codeblocks. +- Wrap adjacent codeblocks in a `` element. +- If a codeblock is preceded by a title, move the title to the codeblock. + Example before: + ``` + **Request** + ```bash + curl -X POST -H "Content-Type: application/json" + ``` + Example after: + ``` + ```bash Request + curl -X POST -H "Content-Type: application/json" + ``` + ``` From 8bbb54a06700ff839d20cfd1b834a861f3faaf08 Mon Sep 17 00:00:00 2001 From: Ben Colborn Date: Thu, 20 Mar 2025 10:59:00 -0700 Subject: [PATCH 07/21] directory name --- {stylecheck => style}/prompt.md | 0 {stylecheck => style}/sample-common.mdx | 0 {stylecheck => style}/sample-developer.mdx | 0 {stylecheck => style}/style-common.md | 0 {stylecheck => style}/style-developer.md | 0 stylecheck.py | 4 ++-- 6 files changed, 2 insertions(+), 2 deletions(-) rename {stylecheck => style}/prompt.md (100%) rename {stylecheck => style}/sample-common.mdx (100%) rename {stylecheck => style}/sample-developer.mdx (100%) rename {stylecheck => style}/style-common.md (100%) rename {stylecheck => style}/style-developer.md (100%) diff --git a/stylecheck/prompt.md b/style/prompt.md similarity index 100% rename from stylecheck/prompt.md rename to style/prompt.md diff --git a/stylecheck/sample-common.mdx b/style/sample-common.mdx similarity index 100% rename from stylecheck/sample-common.mdx rename to style/sample-common.mdx diff --git a/stylecheck/sample-developer.mdx b/style/sample-developer.mdx similarity index 100% rename from stylecheck/sample-developer.mdx rename to style/sample-developer.mdx diff --git a/stylecheck/style-common.md b/style/style-common.md similarity index 100% rename from stylecheck/style-common.md rename to style/style-common.md diff --git a/stylecheck/style-developer.md b/style/style-developer.md similarity index 100% rename from stylecheck/style-developer.md rename to style/style-developer.md diff --git a/stylecheck.py b/stylecheck.py index d9466279..f00296dd 100644 --- a/stylecheck.py +++ b/stylecheck.py @@ -8,7 +8,7 @@ def main(args): with open(args.doc, 'r', encoding="utf-8") as infile: content = infile.read() - style = 'stylecheck/style-common.md' + style = 'style/style-common.md' with open(style, 'r', encoding="utf-8") as infile: style = infile.read() @@ -44,7 +44,7 @@ def main(args): def gen_prompt(content, style): - prompt = 'stylecheck/prompt.md' + prompt = 'style/prompt.md' with open(prompt, 'r') as infile: prompt = infile.read() prompt += "\n\n" From bae045493b7ac0ed6d8ba218dd76082efeff1c73 Mon Sep 17 00:00:00 2001 From: Ben Colborn Date: Thu, 20 Mar 2025 11:44:57 -0700 Subject: [PATCH 08/21] terminology --- style/prompt.md | 2 +- style/sample-common.mdx | 2 +- style/style-developer.md | 1 + style/term-common.csv | 6 ++++++ stylecheck.py | 13 ++++++++++++- 5 files changed, 21 insertions(+), 3 deletions(-) create mode 100644 style/term-common.csv diff --git a/style/prompt.md b/style/prompt.md index 3f35d05c..e7ddba88 100644 --- a/style/prompt.md +++ b/style/prompt.md @@ -1,5 +1,5 @@ ## Prompt -You are an expert technical documentation editor. Analyze the included document and revise it so that it adheres to the following style rules. While you can make suggestions for being more concise, be very careful not to remove any facts from the document. Also do not change the document structure, including removing the title or any headings, unless the original structure violates any of the guidelines. +You are an expert technical documentation editor. Analyze the included document and revise it so that it adheres to the following style rules. While you can make suggestions for being more concise, be very careful not to remove any facts from the document. Also do not change the document structure, including removing the title or headings, unless the original structure violates any of the guidelines. Also check the terminology given in CSV format inside the `` element. In your response, summarize the violations that you fixed. Return the revised markdown inside a separate element `` element, just like in this request, that contains only the document and no other commentary. I’m going to read your suggestions through an API and need to be able to get just the document for the next stage. \ No newline at end of file diff --git a/style/sample-common.mdx b/style/sample-common.mdx index 233e079b..2e6d1816 100644 --- a/style/sample-common.mdx +++ b/style/sample-common.mdx @@ -1,6 +1,6 @@ # DataDog Snap-in -This integration links Datadog's monitoring system with DevRev's incident management, simplifying the process of managing incidents triggered by Datadog alerts. It automatically generates new incidents and updates existing incidents in DevRev using data from Datadog, allowing your team to address issues efficiently without manual tracking. +This integration links Datadog's monitoring system with DevRev's incident management, simplifying the process of managing RevOrg incidents triggered by Datadog alerts. It automatically generates new incidents and updates existing incidents in DevRev using data from Datadog, allowing your team to address issues efficiently without manual tracking. ### System Features diff --git a/style/style-developer.md b/style/style-developer.md index 32475c94..b22e0f3e 100644 --- a/style/style-developer.md +++ b/style/style-developer.md @@ -1,5 +1,6 @@ ## Developer-specific style +- Don't change text inside inline code phrases or code blocks. - Always specify a language for codeblocks. - Wrap adjacent codeblocks in a `` element. - If a codeblock is preceded by a title, move the title to the codeblock. diff --git a/style/term-common.csv b/style/term-common.csv new file mode 100644 index 00000000..88838474 --- /dev/null +++ b/style/term-common.csv @@ -0,0 +1,6 @@ +Deprecated;Approved +foo;example +Devrev;DevRev +RevOrg;workspace +RevUser;customer +snapin;snap-in \ No newline at end of file diff --git a/stylecheck.py b/stylecheck.py index f00296dd..ea073740 100644 --- a/stylecheck.py +++ b/stylecheck.py @@ -18,6 +18,16 @@ def main(args): style += infile.read() style += "\n\n" + term = 'style/term-common.csv' + with open(term, 'r', encoding="utf-8") as infile: + style += "\n\n\n" + style += infile.read() + + if args.term and os.path.exists(args.term): + with open(args.style, 'r', encoding="utf-8") as infile: + style += infile.read() + style += "\n\n\n" + prompt = gen_prompt(content, style) prompt_file = f"temp/prompt.md" with open(prompt_file, 'w', encoding="utf-8") as outfile: @@ -49,7 +59,7 @@ def gen_prompt(content, style): prompt = infile.read() prompt += "\n\n" prompt += style - prompt += "\n\n\n\n" + prompt += "\n\n\n\n" prompt += content prompt += "\n\n\n\n" return prompt @@ -59,6 +69,7 @@ def gen_prompt(content, style): parser = argparse.ArgumentParser(description="Check writing style of markdown file") parser.add_argument('--doc', type=pathlib.Path, required=True) parser.add_argument('--style', type=pathlib.Path) + parser.add_argument('--term', type=pathlib.Path) args = parser.parse_args() main(args) \ No newline at end of file From fcefac64c23d00b04a264a4b6c49adce00691a5e Mon Sep 17 00:00:00 2001 From: Ben Colborn Date: Thu, 20 Mar 2025 16:57:55 -0700 Subject: [PATCH 09/21] refactored --- stylecheck.py | 55 ++++++++++++++++++++++----------------------------- 1 file changed, 24 insertions(+), 31 deletions(-) diff --git a/stylecheck.py b/stylecheck.py index ea073740..12de79d8 100644 --- a/stylecheck.py +++ b/stylecheck.py @@ -5,37 +5,42 @@ def main(args): print(f"Checking style for {args.doc}") - with open(args.doc, 'r', encoding="utf-8") as infile: - content = infile.read() - - style = 'style/style-common.md' - with open(style, 'r', encoding="utf-8") as infile: - style = infile.read() + doc_name, ext = os.path.splitext(os.path.basename(args.doc)) + + with open('style/prompt.md', 'r') as infile: + prompt = infile.read() + prompt += "\n\n" + + with open('style/style-common.md', 'r', encoding="utf-8") as infile: + prompt += infile.read() if args.style and os.path.exists(args.style): with open(args.style, 'r', encoding="utf-8") as infile: - style += "\n\n" - style += infile.read() - style += "\n\n" + prompt += "\n\n" + prompt += infile.read() + prompt += "\n\n" - term = 'style/term-common.csv' - with open(term, 'r', encoding="utf-8") as infile: - style += "\n\n\n" - style += infile.read() + with open('style/term-common.csv', 'r', encoding="utf-8") as infile: + prompt += "\n\n\n" + prompt += infile.read() if args.term and os.path.exists(args.term): with open(args.style, 'r', encoding="utf-8") as infile: - style += infile.read() - style += "\n\n\n" + prompt += infile.read() + prompt += "\n\n\n" - prompt = gen_prompt(content, style) - prompt_file = f"temp/prompt.md" + with open(args.doc, 'r', encoding="utf-8") as infile: + prompt += "\n\n\n\n" + prompt += infile.read() + prompt += "\n\n\n\n" + + prompt_file = f"temp/{doc_name}_prompt.md" with open(prompt_file, 'w', encoding="utf-8") as outfile: outfile.write(prompt) print(f"Wrote prompt to {prompt_file}.") response = llm_client.get_response(prompt) - response_file = "/".join(['temp', 'full_' + os.path.basename(args.doc)]) + response_file = f"temp/{doc_name}_response.md" if (response): with open(response_file, 'w', encoding="utf-8") as outfile: outfile.write(response) @@ -44,7 +49,7 @@ def main(args): print(f"Failed to generate {response_file}.") revision = llm_client.get_lines_between_tags(response, 'document') - revision_file = "/".join(['temp', os.path.basename(args.doc)]) + revision_file = f"temp/{doc_name}_revision.md" if (revision): with open(revision_file, 'w', encoding="utf-8") as outfile: outfile.write(revision) @@ -53,18 +58,6 @@ def main(args): print(f"Failed to generate {revision_file}.") -def gen_prompt(content, style): - prompt = 'style/prompt.md' - with open(prompt, 'r') as infile: - prompt = infile.read() - prompt += "\n\n" - prompt += style - prompt += "\n\n\n\n" - prompt += content - prompt += "\n\n\n\n" - return prompt - - if __name__ == "__main__": parser = argparse.ArgumentParser(description="Check writing style of markdown file") parser.add_argument('--doc', type=pathlib.Path, required=True) From 9085ff36ab569873668de19479c0a18813bfba77 Mon Sep 17 00:00:00 2001 From: Ben Colborn Date: Thu, 20 Mar 2025 17:58:06 -0700 Subject: [PATCH 10/21] get diff between original and revision --- stylecheck.py | 61 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 25 deletions(-) diff --git a/stylecheck.py b/stylecheck.py index 12de79d8..2e92c416 100644 --- a/stylecheck.py +++ b/stylecheck.py @@ -3,10 +3,38 @@ import pathlib import llm_client + +import difflib + +def generate_git_diff(before, after, filename="file.txt"): + + before_lines = before.splitlines(keepends=True) + after_lines = after.splitlines(keepends=True) + + diff = difflib.unified_diff( + before_lines, + after_lines, + fromfile=f"a/{filename}", + tofile=f"b/{filename}", + ) + return "".join(diff) + +def my_writer(content, file, note=None): + if (content): + with open(file, 'w', encoding="utf-8") as outfile: + outfile.write(content) + print(' '.join(['Wrote', note, 'to', file])) + #print(f"Wrote {note} to {file}.") + else: + print(f"Failed to write {file}.") + def main(args): print(f"Checking style for {args.doc}") doc_name, ext = os.path.splitext(os.path.basename(args.doc)) + with open(args.doc, 'r', encoding="utf-8") as infile: + doc = infile.read() + with open('style/prompt.md', 'r') as infile: prompt = infile.read() prompt += "\n\n" @@ -29,34 +57,17 @@ def main(args): prompt += infile.read() prompt += "\n\n\n" - with open(args.doc, 'r', encoding="utf-8") as infile: - prompt += "\n\n\n\n" - prompt += infile.read() - prompt += "\n\n\n\n" + prompt += "\n\n\n\n" + prompt += doc + prompt += "\n\n\n\n" - prompt_file = f"temp/{doc_name}_prompt.md" - with open(prompt_file, 'w', encoding="utf-8") as outfile: - outfile.write(prompt) - print(f"Wrote prompt to {prompt_file}.") - + my_writer(prompt, f"temp/{doc_name}_prompt.md", 'prompt') response = llm_client.get_response(prompt) - response_file = f"temp/{doc_name}_response.md" - if (response): - with open(response_file, 'w', encoding="utf-8") as outfile: - outfile.write(response) - print(f"Wrote response to {response_file}.") - else: - print(f"Failed to generate {response_file}.") - + my_writer(response, f"temp/{doc_name}_response.md", 'response') revision = llm_client.get_lines_between_tags(response, 'document') - revision_file = f"temp/{doc_name}_revision.md" - if (revision): - with open(revision_file, 'w', encoding="utf-8") as outfile: - outfile.write(revision) - print(f"Wrote revision to {revision_file}.") - else: - print(f"Failed to generate {revision_file}.") - + my_writer(revision, f"temp/{doc_name}_revision{ext}", 'revision') + diff = generate_git_diff(doc, revision, doc_name) + my_writer(diff, f"temp/{doc_name}.diff", 'diff') if __name__ == "__main__": parser = argparse.ArgumentParser(description="Check writing style of markdown file") From fab02ebd25dcc611ac1f999e671064d74f8d9377 Mon Sep 17 00:00:00 2001 From: Ben Colborn Date: Thu, 20 Mar 2025 17:58:27 -0700 Subject: [PATCH 11/21] terminology violations --- style/sample-common.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/style/sample-common.mdx b/style/sample-common.mdx index 2e6d1816..26e02694 100644 --- a/style/sample-common.mdx +++ b/style/sample-common.mdx @@ -1,6 +1,6 @@ # DataDog Snap-in -This integration links Datadog's monitoring system with DevRev's incident management, simplifying the process of managing RevOrg incidents triggered by Datadog alerts. It automatically generates new incidents and updates existing incidents in DevRev using data from Datadog, allowing your team to address issues efficiently without manual tracking. +This integration links Datadog's monitoring system with Devrev's incident management, simplifying the process of managing RevOrg incidents triggered by Datadog alerts. It automatically generates new incidents and updates existing incidents in DevRev using data from Datadog, allowing your team to address issues efficiently without manual tracking. ### System Features @@ -42,6 +42,6 @@ selection, and click **Deploy snap-in**. ### References -- [DataDog DevRev snap-in marketplace listing](https://marketplace.devrev.ai/datadog-snapin) +- [DataDog Devrev snap-in marketplace listing](https://marketplace.devrev.ai/datadog-snapin) - [DataDog DevRev snapin documentation](https://devrev.ai/docs/integrations/datadog) - [DataDog API](https://docs.datadoghq.com/api/latest/) \ No newline at end of file From 8ae4b98665ff33d68323a8251e54cadd661444d0 Mon Sep 17 00:00:00 2001 From: Ben Colborn Date: Mon, 24 Mar 2025 14:33:38 -0700 Subject: [PATCH 12/21] US English --- style/sample-common.mdx | 2 +- style/style-common.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/style/sample-common.mdx b/style/sample-common.mdx index 26e02694..9dcb9b32 100644 --- a/style/sample-common.mdx +++ b/style/sample-common.mdx @@ -1,6 +1,6 @@ # DataDog Snap-in -This integration links Datadog's monitoring system with Devrev's incident management, simplifying the process of managing RevOrg incidents triggered by Datadog alerts. It automatically generates new incidents and updates existing incidents in DevRev using data from Datadog, allowing your team to address issues efficiently without manual tracking. +This integration links Datadog's monitoring system with Devrev's incident management, simplifying the process of managing RevOrg incidents triggered by Datadog alerts. It automatically generates new incidents and updates existing incidents in DevRev using data from Datadog, allowing your organisation to address issues efficiently without manual tracking. ### System Features diff --git a/style/style-common.md b/style/style-common.md index 9c5e2059..88f801fa 100644 --- a/style/style-common.md +++ b/style/style-common.md @@ -7,6 +7,7 @@ For the writing style, apply the following descriptive keywords to analyzing the - Vocabulary choice: consistent, technical - Grammar and syntax: structured, flawless, articulate - Descriptive language: clear, concise, informative +- Language variant: standard technical/professional US American English; not British, Indian, or other variants. There are also some very specific style rules that need to be followed: From ed3abf5c9fc0f3e8b73ecd613dbf08f0bc0f01c0 Mon Sep 17 00:00:00 2001 From: Ben Colborn Date: Mon, 24 Mar 2025 15:00:52 -0700 Subject: [PATCH 13/21] refactor --- style/prompt.md | 2 -- stylecheck.py | 43 ++++++++++++++++++++++++------------------- 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/style/prompt.md b/style/prompt.md index e7ddba88..22316c4a 100644 --- a/style/prompt.md +++ b/style/prompt.md @@ -1,5 +1,3 @@ -## Prompt - You are an expert technical documentation editor. Analyze the included document and revise it so that it adheres to the following style rules. While you can make suggestions for being more concise, be very careful not to remove any facts from the document. Also do not change the document structure, including removing the title or headings, unless the original structure violates any of the guidelines. Also check the terminology given in CSV format inside the `` element. In your response, summarize the violations that you fixed. Return the revised markdown inside a separate element `` element, just like in this request, that contains only the document and no other commentary. I’m going to read your suggestions through an API and need to be able to get just the document for the next stage. \ No newline at end of file diff --git a/stylecheck.py b/stylecheck.py index 2e92c416..af3318f0 100644 --- a/stylecheck.py +++ b/stylecheck.py @@ -6,7 +6,7 @@ import difflib -def generate_git_diff(before, after, filename="file.txt"): +def gen_diff(before, after, filename="file.txt"): before_lines = before.splitlines(keepends=True) after_lines = after.splitlines(keepends=True) @@ -19,21 +19,7 @@ def generate_git_diff(before, after, filename="file.txt"): ) return "".join(diff) -def my_writer(content, file, note=None): - if (content): - with open(file, 'w', encoding="utf-8") as outfile: - outfile.write(content) - print(' '.join(['Wrote', note, 'to', file])) - #print(f"Wrote {note} to {file}.") - else: - print(f"Failed to write {file}.") - -def main(args): - print(f"Checking style for {args.doc}") - doc_name, ext = os.path.splitext(os.path.basename(args.doc)) - - with open(args.doc, 'r', encoding="utf-8") as infile: - doc = infile.read() +def gen_prompt(args): with open('style/prompt.md', 'r') as infile: prompt = infile.read() @@ -58,15 +44,34 @@ def main(args): prompt += "\n\n\n" prompt += "\n\n\n\n" - prompt += doc + prompt += args.doc prompt += "\n\n\n\n" - + + return prompt + +def my_writer(content, file, note=None): + if (content): + with open(file, 'w', encoding="utf-8") as outfile: + outfile.write(content) + print(' '.join(['Wrote', note, 'to', file])) + #print(f"Wrote {note} to {file}.") + else: + print(f"Failed to write {file}.") + +def main(args): + print(f"Checking style for {args.doc}") + doc_name, ext = os.path.splitext(os.path.basename(args.doc)) + + with open(args.doc, 'r', encoding="utf-8") as infile: + args.doc = infile.read() + + prompt = gen_prompt(args) my_writer(prompt, f"temp/{doc_name}_prompt.md", 'prompt') response = llm_client.get_response(prompt) my_writer(response, f"temp/{doc_name}_response.md", 'response') revision = llm_client.get_lines_between_tags(response, 'document') my_writer(revision, f"temp/{doc_name}_revision{ext}", 'revision') - diff = generate_git_diff(doc, revision, doc_name) + diff = gen_diff(args.doc, revision, doc_name) my_writer(diff, f"temp/{doc_name}.diff", 'diff') if __name__ == "__main__": From cc8601d448372dfaf0169e3ed9d21650138c5f78 Mon Sep 17 00:00:00 2001 From: Ben Colborn Date: Tue, 25 Mar 2025 11:45:22 -0700 Subject: [PATCH 14/21] improve diff --- stylecheck.py | 48 ++++++++++++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/stylecheck.py b/stylecheck.py index af3318f0..34c5c5ea 100644 --- a/stylecheck.py +++ b/stylecheck.py @@ -2,22 +2,32 @@ import os import pathlib import llm_client - - import difflib -def gen_diff(before, after, filename="file.txt"): +def create_line_diff(old_file, new_file): + with open(old_file, 'r', encoding='utf-8') as f1: + old_lines = f1.readlines() + with open(new_file, 'r', encoding='utf-8') as f2: + new_lines = f2.readlines() - before_lines = before.splitlines(keepends=True) - after_lines = after.splitlines(keepends=True) - - diff = difflib.unified_diff( - before_lines, - after_lines, - fromfile=f"a/{filename}", - tofile=f"b/{filename}", - ) - return "".join(diff) + # Get line-level differences + matcher = difflib.SequenceMatcher(None, old_lines, new_lines) + + # Generate unified diff format + diff_lines = [] + for tag, i1, i2, j1, j2 in matcher.get_opcodes(): + if tag == 'replace': + # Add hunk header + diff_lines.append(f'@@ -{i1+1},{i2-i1} +{j1+1},{j2-j1} @@') + # Add removals + for line in old_lines[i1:i2]: + diff_lines.append('-' + line.rstrip()) + # Add additions + for line in new_lines[j1:j2]: + diff_lines.append('+' + line.rstrip()) + diff_lines.append('') # blank line between hunks + + return '\n'.join(diff_lines) def gen_prompt(args): @@ -44,7 +54,8 @@ def gen_prompt(args): prompt += "\n\n\n" prompt += "\n\n\n\n" - prompt += args.doc + with open(args.doc, 'r', encoding="utf-8") as infile: + prompt += infile.read() prompt += "\n\n\n\n" return prompt @@ -54,7 +65,6 @@ def my_writer(content, file, note=None): with open(file, 'w', encoding="utf-8") as outfile: outfile.write(content) print(' '.join(['Wrote', note, 'to', file])) - #print(f"Wrote {note} to {file}.") else: print(f"Failed to write {file}.") @@ -62,16 +72,14 @@ def main(args): print(f"Checking style for {args.doc}") doc_name, ext = os.path.splitext(os.path.basename(args.doc)) - with open(args.doc, 'r', encoding="utf-8") as infile: - args.doc = infile.read() - prompt = gen_prompt(args) my_writer(prompt, f"temp/{doc_name}_prompt.md", 'prompt') response = llm_client.get_response(prompt) my_writer(response, f"temp/{doc_name}_response.md", 'response') revision = llm_client.get_lines_between_tags(response, 'document') - my_writer(revision, f"temp/{doc_name}_revision{ext}", 'revision') - diff = gen_diff(args.doc, revision, doc_name) + revision_file = f"temp/{doc_name}_revision{ext}" + my_writer(revision, revision_file, 'revision') + diff = create_line_diff(args.doc, revision_file) my_writer(diff, f"temp/{doc_name}.diff", 'diff') if __name__ == "__main__": From 8984abf56ed0fc64e452490b458ec05b4922b88e Mon Sep 17 00:00:00 2001 From: Ben Colborn Date: Tue, 25 Mar 2025 18:56:23 -0700 Subject: [PATCH 15/21] option to skip LLM for testing --- stylecheck.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/stylecheck.py b/stylecheck.py index 34c5c5ea..7abc3e84 100644 --- a/stylecheck.py +++ b/stylecheck.py @@ -74,8 +74,18 @@ def main(args): prompt = gen_prompt(args) my_writer(prompt, f"temp/{doc_name}_prompt.md", 'prompt') - response = llm_client.get_response(prompt) - my_writer(response, f"temp/{doc_name}_response.md", 'response') + response_file = f"temp/{doc_name}_response.md" + if args.llm: + response = llm_client.get_response(prompt) + my_writer(response, response_file, 'response') + else: + try: + with open(response_file, 'r') as infile: + response = infile.read() + print(f"Reading response from {response_file}.") + except: + print(f"LLM response in {response_file} not found. Exiting.") + return revision = llm_client.get_lines_between_tags(response, 'document') revision_file = f"temp/{doc_name}_revision{ext}" my_writer(revision, revision_file, 'revision') @@ -84,9 +94,12 @@ def main(args): if __name__ == "__main__": parser = argparse.ArgumentParser(description="Check writing style of markdown file") - parser.add_argument('--doc', type=pathlib.Path, required=True) parser.add_argument('--style', type=pathlib.Path) parser.add_argument('--term', type=pathlib.Path) + parser.add_argument('--llm', default=True, action=argparse.BooleanOptionalAction) + parser.add_argument('doc', nargs=1, type=pathlib.Path) args = parser.parse_args() + args.doc = str(args.doc[0]) + main(args) \ No newline at end of file From b3b4cde29c0503ece31b028bd33192f677fc8608 Mon Sep 17 00:00:00 2001 From: Ben Colborn Date: Tue, 25 Mar 2025 19:12:45 -0700 Subject: [PATCH 16/21] get the title back --- stylecheck.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/stylecheck.py b/stylecheck.py index 7abc3e84..07e6aab8 100644 --- a/stylecheck.py +++ b/stylecheck.py @@ -29,6 +29,18 @@ def create_line_diff(old_file, new_file): return '\n'.join(diff_lines) + +def restore_title(old, new): + with open(old, 'r') as infile: + old = infile.read() + + old_first = old.splitlines()[0] + new_first = new.splitlines()[0] + if old_first.startswith('#') and not new_first.startswith('#'): + new = old_first + "\n\n" + new + + return new + def gen_prompt(args): with open('style/prompt.md', 'r') as infile: @@ -87,6 +99,7 @@ def main(args): print(f"LLM response in {response_file} not found. Exiting.") return revision = llm_client.get_lines_between_tags(response, 'document') + revision = restore_title(args.doc, revision) revision_file = f"temp/{doc_name}_revision{ext}" my_writer(revision, revision_file, 'revision') diff = create_line_diff(args.doc, revision_file) From b7a73a3151a6dc2ca51caf11399a626ce4d970c1 Mon Sep 17 00:00:00 2001 From: Ben Colborn Date: Wed, 26 Mar 2025 14:05:53 -0700 Subject: [PATCH 17/21] parse response and diff into comments --- llm_client.py | 5 ++++ stylecheck.py | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 78 insertions(+), 1 deletion(-) diff --git a/llm_client.py b/llm_client.py index 722edde8..a14af2af 100644 --- a/llm_client.py +++ b/llm_client.py @@ -32,4 +32,9 @@ def get_response(prompt): def get_lines_between_tags(text, tag): pattern = r'<' + tag + r'>(.*?)<\/' + tag + r'>' matches = re.findall(pattern, text, re.DOTALL) + return "".join([match.strip() for match in matches]) + +def get_lines_before_tag(text, tag): + pattern = r'(.*?)<' + tag + r'>' + matches = re.findall(pattern, text, re.DOTALL) return "".join([match.strip() for match in matches]) \ No newline at end of file diff --git a/stylecheck.py b/stylecheck.py index 07e6aab8..083b7223 100644 --- a/stylecheck.py +++ b/stylecheck.py @@ -3,6 +3,7 @@ import pathlib import llm_client import difflib +import re def create_line_diff(old_file, new_file): with open(old_file, 'r', encoding='utf-8') as f1: @@ -15,6 +16,9 @@ def create_line_diff(old_file, new_file): # Generate unified diff format diff_lines = [] + old_file = old_file.replace('\\', '/') + diff_lines.append(f"--- a/{old_file}") + diff_lines.append(f"+++ b/{old_file}") for tag, i1, i2, j1, j2 in matcher.get_opcodes(): if tag == 'replace': # Add hunk header @@ -25,7 +29,7 @@ def create_line_diff(old_file, new_file): # Add additions for line in new_lines[j1:j2]: diff_lines.append('+' + line.rstrip()) - diff_lines.append('') # blank line between hunks + diff_lines.append('') return '\n'.join(diff_lines) @@ -72,6 +76,71 @@ def gen_prompt(args): return prompt + +def parse_diff_hunks(diff_text): + path = diff_text.split('\n')[0].split('a/')[1] + print(path) + hunks = diff_text.split('@@ ') + comments = [] + + for hunk in hunks[1:]: # Skip first empty element + # Parse hunk header + header_match = re.match(r'^-(\d+),(\d+) \+(\d+),(\d+) @@', hunk) + if not header_match: + continue + + old_start, old_lines, new_start, new_lines = map(int, header_match.groups()) + + # Split hunk content into lines + lines = hunk.split('\n')[1:] # Skip header line + old_line = old_start + new_line = new_start + + # Find consecutive removal/addition pairs + i = 0 + while i < len(lines): + if i + 1 < len(lines) and lines[i].startswith('-') and lines[i+1].startswith('+'): + old_text = lines[i][1:] # Remove the '-' prefix + new_text = lines[i+1][1:] # Remove the '+' prefix + + # Create suggestion comment + comment = { + 'path': {path}, + 'line': new_line, + 'side': 'RIGHT', + 'body': f'```suggestion\n{new_text}\n```' + } + comments.append(comment) + + old_line += 1 + new_line += 1 + i += 2 + else: + if not lines[i].startswith('+'): + old_line += 1 + if not lines[i].startswith('-'): + new_line += 1 + i += 1 + + return comments + + +def make_suggestions(comment, diff): + + print(comment) + suggestions = parse_diff_hunks(diff) + + for suggestion in suggestions: + print(suggestion) + + """ + requests.post( + f'https://api.github.com/repos/OWNER/REPO/pulls/PULL_NUMBER/comments', + headers={'Authorization': f'token {github_token}'}, + json=comment + ) + """ + def my_writer(content, file, note=None): if (content): with open(file, 'w', encoding="utf-8") as outfile: @@ -98,6 +167,7 @@ def main(args): except: print(f"LLM response in {response_file} not found. Exiting.") return + comment = llm_client.get_lines_before_tag(response, 'document') revision = llm_client.get_lines_between_tags(response, 'document') revision = restore_title(args.doc, revision) revision_file = f"temp/{doc_name}_revision{ext}" @@ -105,6 +175,8 @@ def main(args): diff = create_line_diff(args.doc, revision_file) my_writer(diff, f"temp/{doc_name}.diff", 'diff') + make_suggestions(comment, diff) + if __name__ == "__main__": parser = argparse.ArgumentParser(description="Check writing style of markdown file") parser.add_argument('--style', type=pathlib.Path) From 52eadfc75688f4457e708b7b86cc7a4959e45c51 Mon Sep 17 00:00:00 2001 From: Ben Colborn Date: Wed, 26 Mar 2025 14:05:53 -0700 Subject: [PATCH 18/21] parse response and diff into comments --- llm_client.py | 5 +++ stylecheck.py | 87 ++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 91 insertions(+), 1 deletion(-) diff --git a/llm_client.py b/llm_client.py index 722edde8..a14af2af 100644 --- a/llm_client.py +++ b/llm_client.py @@ -32,4 +32,9 @@ def get_response(prompt): def get_lines_between_tags(text, tag): pattern = r'<' + tag + r'>(.*?)<\/' + tag + r'>' matches = re.findall(pattern, text, re.DOTALL) + return "".join([match.strip() for match in matches]) + +def get_lines_before_tag(text, tag): + pattern = r'(.*?)<' + tag + r'>' + matches = re.findall(pattern, text, re.DOTALL) return "".join([match.strip() for match in matches]) \ No newline at end of file diff --git a/stylecheck.py b/stylecheck.py index 07e6aab8..c8f73ab2 100644 --- a/stylecheck.py +++ b/stylecheck.py @@ -3,6 +3,9 @@ import pathlib import llm_client import difflib +import re +import requests + def create_line_diff(old_file, new_file): with open(old_file, 'r', encoding='utf-8') as f1: @@ -15,6 +18,9 @@ def create_line_diff(old_file, new_file): # Generate unified diff format diff_lines = [] + old_file = old_file.replace('\\', '/') + diff_lines.append(f"--- a/{old_file}") + diff_lines.append(f"+++ b/{old_file}") for tag, i1, i2, j1, j2 in matcher.get_opcodes(): if tag == 'replace': # Add hunk header @@ -25,7 +31,7 @@ def create_line_diff(old_file, new_file): # Add additions for line in new_lines[j1:j2]: diff_lines.append('+' + line.rstrip()) - diff_lines.append('') # blank line between hunks + diff_lines.append('') return '\n'.join(diff_lines) @@ -72,6 +78,75 @@ def gen_prompt(args): return prompt + +def parse_diff_hunks(diff_text): + path = diff_text.split('\n')[0].split('a/')[1] + print(path) + hunks = diff_text.split('@@ ') + comments = [] + + for hunk in hunks[1:]: # Skip first empty element + # Parse hunk header + header_match = re.match(r'^-(\d+),(\d+) \+(\d+),(\d+) @@', hunk) + if not header_match: + continue + + old_start, old_lines, new_start, new_lines = map(int, header_match.groups()) + + # Split hunk content into lines + lines = hunk.split('\n')[1:] # Skip header line + old_line = old_start + new_line = new_start + + # Find consecutive removal/addition pairs + i = 0 + while i < len(lines): + if i + 1 < len(lines) and lines[i].startswith('-') and lines[i+1].startswith('+'): + old_text = lines[i][1:] # Remove the '-' prefix + new_text = lines[i+1][1:] # Remove the '+' prefix + + # Create suggestion comment + comment = { + 'path': path, + 'line': new_line, + 'side': 'RIGHT', + 'body': f'```suggestion\n{new_text}\n```' + } + comments.append(comment) + + old_line += 1 + new_line += 1 + i += 2 + else: + if not lines[i].startswith('+'): + old_line += 1 + if not lines[i].startswith('-'): + new_line += 1 + i += 1 + + return comments + + +def post_review_comment(owner, repo, pr_number, comment): + comment['commit_id'] = os.environ.get('COMMIT') + + print("Comment before JSON conversion:") + for key, value in comment.items(): + print(f"{key}: {type(value)} = {value}") + + url = f'https://api.github.com/repos/{owner}/{repo}/pulls/{pr_number}/comments' + headers = { + 'Authorization': f"token {os.environ.get('STYLECHECK')}", + 'Accept': 'application/vnd.github.v3+json' + } + try: + response = requests.post(url, headers=headers, json=comment) + response.raise_for_status() + + except Exception as e: + print( + f"Failed to post comment. Error: {type(e)} {e}") + def my_writer(content, file, note=None): if (content): with open(file, 'w', encoding="utf-8") as outfile: @@ -98,6 +173,7 @@ def main(args): except: print(f"LLM response in {response_file} not found. Exiting.") return + comment = llm_client.get_lines_before_tag(response, 'document') revision = llm_client.get_lines_between_tags(response, 'document') revision = restore_title(args.doc, revision) revision_file = f"temp/{doc_name}_revision{ext}" @@ -105,11 +181,20 @@ def main(args): diff = create_line_diff(args.doc, revision_file) my_writer(diff, f"temp/{doc_name}.diff", 'diff') + suggestions = parse_diff_hunks(diff) + if (args.suggest): + for suggestion in suggestions: + post_review_comment(os.environ.get('OWNER'), os.environ.get('REPO'), os.environ.get('PR'), suggestion) + else: + for suggestion in suggestions: + print(suggestion) + if __name__ == "__main__": parser = argparse.ArgumentParser(description="Check writing style of markdown file") parser.add_argument('--style', type=pathlib.Path) parser.add_argument('--term', type=pathlib.Path) parser.add_argument('--llm', default=True, action=argparse.BooleanOptionalAction) + parser.add_argument('--suggest', default=True, action=argparse.BooleanOptionalAction) parser.add_argument('doc', nargs=1, type=pathlib.Path) args = parser.parse_args() From 656c853ff9685803169daf2c2776937b414080e8 Mon Sep 17 00:00:00 2001 From: Ben Colborn Date: Mon, 31 Mar 2025 19:52:39 -0700 Subject: [PATCH 19/21] fix line calculation --- stylecheck.py | 46 +++++++++++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/stylecheck.py b/stylecheck.py index c8f73ab2..9e27b222 100644 --- a/stylecheck.py +++ b/stylecheck.py @@ -85,48 +85,56 @@ def parse_diff_hunks(diff_text): hunks = diff_text.split('@@ ') comments = [] - for hunk in hunks[1:]: # Skip first empty element - # Parse hunk header + for hunk in hunks[1:]: header_match = re.match(r'^-(\d+),(\d+) \+(\d+),(\d+) @@', hunk) if not header_match: continue old_start, old_lines, new_start, new_lines = map(int, header_match.groups()) - - # Split hunk content into lines lines = hunk.split('\n')[1:] # Skip header line + old_line = old_start new_line = new_start - # Find consecutive removal/addition pairs i = 0 while i < len(lines): - if i + 1 < len(lines) and lines[i].startswith('-') and lines[i+1].startswith('+'): - old_text = lines[i][1:] # Remove the '-' prefix - new_text = lines[i+1][1:] # Remove the '+' prefix - - # Create suggestion comment + # Collect consecutive removed lines + removed_lines = [] + while i < len(lines) and lines[i].startswith('-'): + removed_lines.append(lines[i][1:]) # Store without the '-' + i += 1 + + # Collect consecutive added lines + added_lines = [] + start_line = new_line # Remember where the addition starts + while i < len(lines) and lines[i].startswith('+'): + added_lines.append(lines[i][1:]) # Store without the '+' + i += 1 + + # If we have both removed and added lines, create a suggestion + if removed_lines and added_lines: + added_lines = "\n".join(added_lines) comment = { 'path': path, - 'line': new_line, + 'start_line': start_line, + 'line': start_line + len(added_lines) - 1, # End line of the addition 'side': 'RIGHT', - 'body': f'```suggestion\n{new_text}\n```' + 'body': f'```suggestion\n{added_lines}\n```' } comments.append(comment) + # Adjust line numbers + old_line += len(removed_lines) + new_line += len(added_lines) + + # Skip context lines + while i < len(lines) and not (lines[i].startswith('-') or lines[i].startswith('+')): old_line += 1 new_line += 1 - i += 2 - else: - if not lines[i].startswith('+'): - old_line += 1 - if not lines[i].startswith('-'): - new_line += 1 i += 1 return comments - def post_review_comment(owner, repo, pr_number, comment): comment['commit_id'] = os.environ.get('COMMIT') From f8aafab6549a39efad41b555d0fd550c8830233e Mon Sep 17 00:00:00 2001 From: Ben Colborn Date: Mon, 31 Mar 2025 19:52:39 -0700 Subject: [PATCH 20/21] fix line calculation --- stylecheck.py | 69 ++++++++++++++++++++++++--------------------------- 1 file changed, 33 insertions(+), 36 deletions(-) diff --git a/stylecheck.py b/stylecheck.py index c8f73ab2..acc12ac3 100644 --- a/stylecheck.py +++ b/stylecheck.py @@ -5,6 +5,7 @@ import difflib import re import requests +import json def create_line_diff(old_file, new_file): @@ -81,59 +82,54 @@ def gen_prompt(args): def parse_diff_hunks(diff_text): path = diff_text.split('\n')[0].split('a/')[1] - print(path) + print(f"Constructing comments for {path}.") hunks = diff_text.split('@@ ') comments = [] - for hunk in hunks[1:]: # Skip first empty element - # Parse hunk header + for hunk in hunks[1:]: header_match = re.match(r'^-(\d+),(\d+) \+(\d+),(\d+) @@', hunk) if not header_match: continue old_start, old_lines, new_start, new_lines = map(int, header_match.groups()) - - # Split hunk content into lines lines = hunk.split('\n')[1:] # Skip header line - old_line = old_start - new_line = new_start - # Find consecutive removal/addition pairs i = 0 while i < len(lines): - if i + 1 < len(lines) and lines[i].startswith('-') and lines[i+1].startswith('+'): - old_text = lines[i][1:] # Remove the '-' prefix - new_text = lines[i+1][1:] # Remove the '+' prefix - - # Create suggestion comment + # Collect consecutive removed lines + removed_lines = [] + while i < len(lines) and lines[i].startswith('-'): + removed_lines.append(lines[i][1:]) + i += 1 + + # Collect consecutive added lines + added_lines = [] + while i < len(lines) and lines[i].startswith('+'): + added_lines.append(lines[i][1:]) + i += 1 + + # If we have both removed and added lines, create a suggestion + if removed_lines and added_lines: + added_lines = "\n".join(added_lines) comment = { 'path': path, - 'line': new_line, + 'line': old_start + old_lines - 1, 'side': 'RIGHT', - 'body': f'```suggestion\n{new_text}\n```' + 'body': f'```suggestion\n{added_lines}\n```' } + if old_start < comment['line']: + comment['start_line'] = old_start comments.append(comment) - - old_line += 1 - new_line += 1 - i += 2 - else: - if not lines[i].startswith('+'): - old_line += 1 - if not lines[i].startswith('-'): - new_line += 1 + + # Skip context lines + while i < len(lines) and not (lines[i].startswith('-') or lines[i].startswith('+')): i += 1 return comments - def post_review_comment(owner, repo, pr_number, comment): comment['commit_id'] = os.environ.get('COMMIT') - print("Comment before JSON conversion:") - for key, value in comment.items(): - print(f"{key}: {type(value)} = {value}") - url = f'https://api.github.com/repos/{owner}/{repo}/pulls/{pr_number}/comments' headers = { 'Authorization': f"token {os.environ.get('STYLECHECK')}", @@ -142,15 +138,18 @@ def post_review_comment(owner, repo, pr_number, comment): try: response = requests.post(url, headers=headers, json=comment) response.raise_for_status() - + print(f"Posted comment on line {comment['line']}.") except Exception as e: print( f"Failed to post comment. Error: {type(e)} {e}") -def my_writer(content, file, note=None): - if (content): +def my_writer(data, file, note=None): + if (data): with open(file, 'w', encoding="utf-8") as outfile: - outfile.write(content) + if type(data) is str: + outfile.write(data) + else: + json.dump(data, outfile, indent=4) print(' '.join(['Wrote', note, 'to', file])) else: print(f"Failed to write {file}.") @@ -182,12 +181,10 @@ def main(args): my_writer(diff, f"temp/{doc_name}.diff", 'diff') suggestions = parse_diff_hunks(diff) + my_writer(suggestions, f"temp/{doc_name}_suggestions.json", 'suggestions') if (args.suggest): for suggestion in suggestions: post_review_comment(os.environ.get('OWNER'), os.environ.get('REPO'), os.environ.get('PR'), suggestion) - else: - for suggestion in suggestions: - print(suggestion) if __name__ == "__main__": parser = argparse.ArgumentParser(description="Check writing style of markdown file") From 03c98c983bf76c61dee5b8f9c585c8199b17d0d0 Mon Sep 17 00:00:00 2001 From: Ben Colborn Date: Mon, 31 Mar 2025 21:07:49 -0700 Subject: [PATCH 21/21] post overall comment --- stylecheck.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/stylecheck.py b/stylecheck.py index acc12ac3..69ff100e 100644 --- a/stylecheck.py +++ b/stylecheck.py @@ -6,6 +6,7 @@ import re import requests import json +import time def create_line_diff(old_file, new_file): @@ -130,7 +131,15 @@ def parse_diff_hunks(diff_text): def post_review_comment(owner, repo, pr_number, comment): comment['commit_id'] = os.environ.get('COMMIT') - url = f'https://api.github.com/repos/{owner}/{repo}/pulls/{pr_number}/comments' + if comment.get('line'): + # Suggestion + url = f'https://api.github.com/repos/{owner}/{repo}/pulls/{pr_number}/comments' + message = f"Posted comment on line {comment['line']}." + else: + # Timeline comment + url = f'https://api.github.com/repos/{owner}/{repo}/issues/{pr_number}/comments' + message = f"Posted comment on timeline." + headers = { 'Authorization': f"token {os.environ.get('STYLECHECK')}", 'Accept': 'application/vnd.github.v3+json' @@ -138,7 +147,7 @@ def post_review_comment(owner, repo, pr_number, comment): try: response = requests.post(url, headers=headers, json=comment) response.raise_for_status() - print(f"Posted comment on line {comment['line']}.") + print(message) except Exception as e: print( f"Failed to post comment. Error: {type(e)} {e}") @@ -172,7 +181,8 @@ def main(args): except: print(f"LLM response in {response_file} not found. Exiting.") return - comment = llm_client.get_lines_before_tag(response, 'document') + comment_text = llm_client.get_lines_before_tag(response, 'document') + comment_text = "✨ Comment from AI reviewer ✨\n\n" + comment_text revision = llm_client.get_lines_between_tags(response, 'document') revision = restore_title(args.doc, revision) revision_file = f"temp/{doc_name}_revision{ext}" @@ -183,7 +193,9 @@ def main(args): suggestions = parse_diff_hunks(diff) my_writer(suggestions, f"temp/{doc_name}_suggestions.json", 'suggestions') if (args.suggest): + post_review_comment(os.environ.get('OWNER'), os.environ.get('REPO'), os.environ.get('PR'), {'body': comment_text}) for suggestion in suggestions: + time.sleep(1) post_review_comment(os.environ.get('OWNER'), os.environ.get('REPO'), os.environ.get('PR'), suggestion) if __name__ == "__main__":