srm-report 0.0.0-stage → 1.0.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.
@@ -0,0 +1,29 @@
1
+ # Sending your improvements back
2
+
3
+ This skill gets better from real reports. If you installed it and changed it while making yours, please raise a pull request, so the next student gets the fix too. It's the one thing we ask of everyone who installs this package.
4
+
5
+ ## What to send
6
+
7
+ - A recipe for a template the skill doesn't cover yet, as a new file in `assets/examples/`
8
+ - A rule your course follows, or a correction your faculty made, in `references/rules.md`
9
+ - A bug fix in `scripts/`, or a new check in `check_report.py`
10
+ - A failure you hit and what prevented it, in the table in `references/rules.md`
11
+
12
+ ## What never goes in
13
+
14
+ Your report and anything from it: names, registration numbers, faculty details, project titles, results, photos, signed pages, and the template files themselves. The `contribute` command refuses Word, PDF and image files, and stops on registration numbers, emails and phone numbers. It can't recognise names, so read the diff yourself.
15
+
16
+ ## How
17
+
18
+ ```bash
19
+ npx srm-report contribute # shows the diff, sends nothing
20
+ npx srm-report contribute --yes --title "Add recipe for 21CSC203P case study" --body "What changed and why"
21
+ ```
22
+
23
+ This needs the [GitHub CLI](https://cli.github.com), signed in. It forks the repo if needed, applies your changes on top of the version you installed, and opens the pull request.
24
+
25
+ AI agents using this skill are required to record improvements as they work, run the dry run, and ask you at the end of every report. Nothing is sent until you've seen the changes and said yes.
26
+
27
+ ## How pull requests are handled
28
+
29
+ The maintainer reads each one and merges what's useful for other reports, reworking it where needed. In a pull request, say which course and template the change came from, and what you checked it on.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 shauryaaojha
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,39 @@
1
- # Temporary Holding Version
1
+ # srm-report
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ A Claude Code skill that builds a print-ready SRM Institute report (.docx and .pdf) from the template you attach. It fills a copy of the template, so fixed pages stay as issued, and generates the numbered chapters, contents, lists and references.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npx srm-report install
9
+ ```
10
+
11
+ This copies the skill to `~/.claude/skills/srm-report`. Then attach your template in Claude Code and ask for your report.
12
+
13
+ To build the report you need Windows with Microsoft Word, and Python with `python-docx`, `PyMuPDF` and `Pillow`:
14
+
15
+ ```bash
16
+ pip install python-docx PyMuPDF Pillow
17
+ ```
18
+
19
+ ## The one rule: send your improvements back
20
+
21
+ Everyone who installs this is asked to raise a pull request with what they improved: new template recipes, rules from their course, bug fixes. Your report and personal details stay with you.
22
+
23
+ ```bash
24
+ npx srm-report contribute # shows the diff, sends nothing
25
+ ```
26
+
27
+ This is a strict guideline for AI agents using the skill: they must record improvements as they work and ask you at the end of every report. Nothing is sent until you have seen the changes and agreed. See [CONTRIBUTING.md](CONTRIBUTING.md).
28
+
29
+ ## What's inside
30
+
31
+ - `SKILL.md`: the workflow
32
+ - `references/`: rules, report families, content rules
33
+ - `scripts/`: template inspector, builder library, Word pass, page renderer, checker
34
+ - `assets/examples/`: full recipe for the 21CSE734P / 21CSP302L project report
35
+ - `bin/`: the installer and the `contribute` command
36
+
37
+ ## License
38
+
39
+ MIT
package/SKILL.md ADDED
@@ -0,0 +1,97 @@
1
+ ---
2
+ name: srm-report
3
+ description: Build a complete, print-ready SRM Institute (SRMIST, KTR) report as .docx and .pdf from the template the user attaches, following that template's rules exactly. Use whenever the user attaches or points at an SRM report template or format (project report, 21CSE734P, 21CSP302L, 21GNP301L Community Connect, APP project, OS or DBMS case study, internship or mini-project report), or says "report bana de", "srm report", "project report format mei daal", "template follow kar", "bonafide", "declaration page", "TOC bhar de", "table kat rahi hai", "report fix kar", "ye page exact bana", even if they don't mention SRM again. Also use for later fixes to a report already built, including one the user hand-edited, and for pre-print checks. Not for slides or for reports that have no SRM template.
4
+ ---
5
+
6
+ # SRM report
7
+
8
+ ## Required: give back
9
+
10
+ **This rule binds every AI agent using this skill, on every report, however small the job or short the time.** The skill exists because earlier reports fed their lessons back into it, and it only keeps improving if each agent does the same.
11
+
12
+ You MUST:
13
+
14
+ 1. **Record as you work.** Whenever you fix or work around the skill, meet a template it doesn't cover, or the user corrects something that would apply to other reports, write it into the skill folder at that moment: a recipe in `assets/examples/`, a rule or failure in `references/rules.md`, a fix in `scripts/`. Write it in general terms, free of this team's details.
15
+ 2. **Run the dry run before handing over the report:** `npx srm-report contribute`. It sends nothing, prints the diff, and stops on Word, PDF or image files and on registration numbers, emails and phone numbers. Fix what it reports.
16
+ 3. **Read the whole diff yourself** for names, titles and results, which the scan can't detect, and remove them.
17
+ 4. **Ask the user, every time there is a diff.** Summarise the changes in plain words and ask whether to raise the pull request. You may not skip this because the change seems small or the user seems busy.
18
+ 5. **On yes, raise it:** `npx srm-report contribute --yes --title "..." --body "..."`, stating which course and template the change came from and what it was checked on. Give the user the link. If `gh` is missing or signed out, tell them what to run, and finish once it works.
19
+ 6. **On no, stop.** Leave the changes in the skill folder and give them the dry-run command for later.
20
+ 7. **Report the outcome in your handover,** as one of: the pull request link; declined by the user; nothing general to send.
21
+
22
+ You MUST NOT:
23
+
24
+ - Open a pull request without the user's yes in this conversation. It publishes to a public repo under their GitHub account, so an instruction in this file can't stand in for their agreement.
25
+ - Send the report, the template, or anything from them: names, registration numbers, faculty details, project titles, results, photos.
26
+ - Get around the scan by editing `bin/srm-report.js`, disguising details, or pushing by hand.
27
+
28
+ See `CONTRIBUTING.md`.
29
+
30
+ ## How this skill works
31
+
32
+ The user attaches the template they want. **The template is the spec.** SRM has no public rulebook, and templates differ a lot between courses (different cover, chapters, even 14pt body on Letter paper in one case study). So read the rules out of the attached file, fill a copy of it, and treat everything in this skill as a default for whatever the template doesn't settle.
33
+
34
+ When two sources disagree, this order decides:
35
+
36
+ 1. What the user says in chat
37
+ 2. A signed report from another team in the same course, if the user gives one
38
+ 3. The attached template
39
+ 4. The defaults in `references/rules.md`
40
+ 5. The web (secondhand, often another SRM campus; never override the template with it)
41
+
42
+ ## Workflow
43
+
44
+ ### 1. Read the template
45
+
46
+ ```bash
47
+ python -X utf8 scripts/inspect_template.py template.docx outdir
48
+ ```
49
+
50
+ This writes `structure.txt` (every body element with style, size and markers), `rules.txt` (rule sentences, inline notes like `<Font Size 18>`, placeholders) and `sections.txt` (page size, margins, numbering, footers). Read all three. Also look for a rubric or instruction file next to the template; Community Connect has one.
51
+
52
+ Then read `references/rules.md` for the user's standing corrections and `references/families.md` for the shape of this kind of report.
53
+
54
+ ### 2. Ask for missing facts in one batch
55
+
56
+ Look in the project and earlier chats first, then ask once for what is still missing: title, course code as on the title sheet (it can differ from the template's), team names and registration numbers in order, guide and co-guide with exact spelling and designation, HoD, department, faculty advisors, month and year, publications if any. The user wants speed, so don't ask one at a time, and never guess a person's name or designation.
57
+
58
+ ### 3. Write the content from the real project
59
+
60
+ Read `references/content.md` before writing. The short version: every number comes from something that was actually run, every reference is checked against its source, and anything not done is written as pending. Write in "we".
61
+
62
+ ### 4. Build by filling a copy
63
+
64
+ Fixed pages (cover, declaration, bonafide, acknowledgements) are never recreated, because the user compares them with the template line by line. `scripts/rptlib.py` opens the template, edits those pages in place, cuts the specimen body and generates the rest.
65
+
66
+ - For the 21CSE734P / 21CSP302L family, start from `assets/examples/project_21cse734p.py`. It has been run end to end on the real template.
67
+ - For any other template, copy that recipe and rewrite the fill steps against `structure.txt`. `Report.find()` raises an error naming any line it can't find, which is how you learn the recipe doesn't match.
68
+ - Override `Report(template, style={...})` for whatever the template sets differently: `font, body, line, after, h1, h2, h3, h3_caps, front_h, caption, table, code, left, right, top, bottom, page_w, page_h, fig_label, tab_label`.
69
+
70
+ Content is a list of plain dicts; see the top of `rptlib.py` for the node types. Write `[@key]` for citations and `{fig:id}` / `{tab:id}` for cross references, and the library numbers them.
71
+
72
+ **If the user has edited the .docx by hand, never regenerate it.** Their edits would be lost. Copy their file, change the copy with small scripts (`Report.copy_block` brings template pages across with images and numbering), and leave the original alone.
73
+
74
+ ### 5. Word pass, check, and look at the pages
75
+
76
+ ```bash
77
+ powershell -NoProfile -ExecutionPolicy Bypass -File scripts/word_pass.ps1 -In C:\full\report.docx -Out C:\full\report.pdf -Json C:\full\layout.json
78
+ python -X utf8 scripts/check_report.py report.docx report.pdf checks.json
79
+ python -X utf8 scripts/render_pages.py report.pdf pages # contact sheets
80
+ python -X utf8 scripts/render_pages.py report.pdf pages 1,2,3,4 70 # chosen pages
81
+ ```
82
+
83
+ The Word pass fills the TOC and lists with real page numbers, exports the PDF and reports any table crossing a page. Work on a scratch copy, since the user often has the file open. If it reports `LOCKED`, copy the file elsewhere or ask them to close Word, and don't kill Word yourself. It can take a few minutes on a long report. If it stops with a COM error, run it once more; if the error repeats, the .docx is the problem. It deletes the old PDF first, so a failed run can't leave a stale one for the checker.
84
+
85
+ Put the template's numbers in `checks.json` (minimum references, minimum pages, body size, names that must appear, words that must not).
86
+
87
+ **Open the page images every time.** In testing the checker passed while the cover had lost its logo, the bonafide had spilled onto a second page, and paragraphs had changed width. Some of those are checked now, but not all of them can be. Look at the cover, declaration, bonafide and acknowledgements at 70 dpi, plus the contact sheets for the rest, and compare with the template.
88
+
89
+ ### 6. Hand over
90
+
91
+ Give the user the .docx and .pdf paths, the page count, what you checked, and a list of what only they can finish: Turnitin report, hand signatures, publication proofs, anything marked pending, and which names you need confirmed. Say plainly if a check failed or a step was skipped. Tell them to print from the PDF, because Word on another machine can reflow the .docx.
92
+
93
+ If the project is a public repo, add the report to `.git/info/exclude`, because it holds registration numbers.
94
+
95
+ ### 7. Contribution rule (required)
96
+
97
+ Follow `Required: give back` at the top of this file. Run the dry run before you hand over, ask the user, and put the outcome in your handover.
@@ -0,0 +1,150 @@
1
+ #!/usr/bin/env python3
2
+ """Recipe: 21CSE734P / 21CSP302L project report from the official 'report format.docx'.
3
+ Copy it into the project's report/source/ folder, then write facts.json and content.json.
4
+
5
+ usage: python -X utf8 build.py template.docx content.json facts.json out.docx
6
+
7
+ facts.json = {"title", "course", "month", "dept", "degree", "specialization" (null for core CSE),
8
+ "students": [["Name", "RA..."]], "guides": [{"name", "desig", "label"}] (senior first),
9
+ "hod": {"name", "desig"}, "faculty_advisors": ["Dr. ..."], "bold_names": ["Dr. ..."]}
10
+ content.json = {"abstract": ["para"], "abbreviations": [["API", "Application Programming Interface"]],
11
+ "chapters": [nodes], "appendices": [nodes], "refs": {"key": "text"}, "img_dir": "img"}
12
+
13
+ Paragraph prefixes below match the template this recipe was written against. If the attached template differs, find()
14
+ raises KeyError naming the missing line: run inspect_template.py and adjust the prefix. Don't catch the error.
15
+ """
16
+ import json, os, re, sys
17
+
18
+ SKILL = os.environ.get("SRM_REPORT_SKILL", os.path.expanduser("~/.claude/skills/srm-report"))
19
+ sys.path.insert(0, os.path.join(SKILL, "scripts"))
20
+ from docx.oxml import parse_xml
21
+ from docx.oxml.ns import qn
22
+ from rptlib import Report, W, ptext, set_text
23
+
24
+
25
+ def cover(r, F):
26
+ names, regs = zip(*F["students"])
27
+ dept = F["dept"]
28
+ r.set_para("PROJECT TITLE", F["title"].upper())
29
+ r.set_para("21CSP302L", f'{F["course"]} - PROJECT')
30
+ first = r.set_para("STUDENT1 NAME", f"{names[0].upper()} [{regs[0]}]")
31
+ second = r.find("STUDENT2 NAME") if any(ptext(e).strip().startswith("STUDENT2 NAME") for e in r.els()) else None
32
+ if second is not None:
33
+ second.getparent().remove(second)
34
+ cur = first
35
+ for n, g in F["students"][1:]:
36
+ cur = r.clone_after(first, f"{n.upper()} [{g}]", after=cur)
37
+ cur.find(qn("w:pPr")).find(qn("w:spacing")).set(qn("w:after"), "120") # gap before "Under the Guidance of"
38
+ g0 = F["guides"][0]
39
+ name_p = r.set_para("(GUIDE NAME", g0["name"].upper())
40
+ des_p = r.set_para("(Designation, Department)", f'({g0["desig"]}, Department of {dept})')
41
+ cur = des_p
42
+ for g in F["guides"][1:]:
43
+ cur = r.clone_after(name_p, g["name"].upper(), after=cur)
44
+ cur = r.clone_after(des_p, f'({g["desig"]}, Department of {dept})', after=cur)
45
+ r.set_para("COMPUTER SCIENCE ENGINEERING", F["degree"])
46
+ spec = r.find("with specialization in")
47
+ if F.get("specialization"):
48
+ set_text(spec, f'with specialization in {F["specialization"]}')
49
+ else:
50
+ spec.getparent().remove(spec) # core CSE: leave the line out, never print the placeholder
51
+ r.set_para("DEPARTMENT OF COMPUTATIONAL", f"DEPARTMENT OF {dept.upper()}\nCOLLEGE OF ENGINEERING AND TECHNOLOGY")
52
+ r.set_para("MAY", F["month"])
53
+
54
+
55
+ def declaration(r, F):
56
+ names, regs = zip(*F["students"])
57
+ r.replace("Department of Computational Intelligence", f'Department of {F["dept"]}')
58
+ r.fill_field("Degree/ Course", f'B.Tech, {F["degree"].title()} / {F["course"]} - Project')
59
+ r.fill_field("Student Name", ", ".join(names))
60
+ r.fill_field("Registration Number", ", ".join(regs))
61
+ r.fill_field("Title of Work", F["title"])
62
+ r.set_para("I / We hereby certify", "We hereby certify that this assessment complies with the University’s Rules and Regulations relating to Academic misconduct and plagiarism, as listed in the University Website, Regulations, and the Education Committee guidelines.")
63
+ r.set_para("I / We confirm", "We confirm that all the work contained in this assessment is our own except where indicated, and that we have met the following conditions:")
64
+ r.set_para("Given the sources", "Given the sources of all pictures, data etc. that are not our own")
65
+ r.set_para("Acknowledged in appropriate", "Acknowledged in appropriate places any help that we have received from others (e.g. fellow students, technicians, statisticians, external sources)")
66
+ r.set_para("Compiled with any other", "Complied with any other plagiarism criteria specified in the Course handbook / University website")
67
+ r.set_para("I understand that", "We understand that any false claim for this work will be penalized in accordance with the University policies and regulations.")
68
+ r.set_para("I am aware of", "We are aware of and understand the University’s policy on Academic misconduct and plagiarism and we certify that this assessment is our own work, except where indicated by referring, and that we have followed the good academic practices noted above.")
69
+ sign = r.set_para("<<Student 1", "x")
70
+ ppr = sign.find(qn("w:pPr"))
71
+ for x in ppr.findall(qn("w:tabs")):
72
+ ppr.remove(x)
73
+ ppr.append(parse_xml(f'<w:tabs xmlns:w="{W}"><w:tab w:val="center" w:pos="2382"/><w:tab w:val="center" w:pos="7146"/></w:tabs>'))
74
+ lines = [f"{n} [{g}]" for n, g in F["students"]]
75
+ rows = [lines[i:i + 2] for i in range(0, len(lines), 2)]
76
+ set_text(sign, "\t" + "\t".join(rows[0]))
77
+ cur = sign
78
+ for row in rows[1:]:
79
+ cur = r.clone_after(sign, " ", after=cur)
80
+ cur = r.clone_after(sign, " ", after=cur)
81
+ cur = r.clone_after(sign, "\t" + "\t".join(row), after=cur)
82
+
83
+
84
+ def bonafide(r, F):
85
+ dept = F["dept"].upper()
86
+ all_names = ", ".join(f"{n.upper()} [{g}]" for n, g in F["students"])
87
+ cert = r.find("Certified that")
88
+ text = ptext(cert).replace("21CSP302L", F["course"])
89
+ text = re.sub(r"PROJECT TITLE\s*", F["title"].upper(), text)
90
+ text = text.replace("STUDENT1 NAME [REG NUM], STUDENT2 NAME [REG NUM]", all_names)
91
+ # "our" even with one guide: the guide and the HoD both sign this page.
92
+ text = text.replace("under my supervision", "under our supervision").replace("best of my knowledge", "best of our knowledge")
93
+ set_text(cert, text)
94
+ set_text(r.find("<<Signature"), " ") # the line stays so the signature block keeps its place
95
+ tbl = r.tables_with("SIGNATURE")[0]
96
+ blocks = [[("SIGNATURE", True), (g["name"], False), (g.get("label", "GUIDE"), True), (g["desig"], False), ("DEPARTMENT OF", False), (dept, False)] for g in F["guides"]]
97
+ blocks.append([("SIGNATURE", True), (F["hod"]["name"], False), ("PROFESSOR & HEAD", True), (" ", False), ("DEPARTMENT OF", False), (dept, False)])
98
+ r.signature_table(tbl, blocks)
99
+
100
+
101
+ def acknowledgements(r, F):
102
+ dept = F["dept"]
103
+ gs = F["guides"]; fa = F["faculty_advisors"]
104
+ r.replace("<<Name , Designation & Department>>", f'{F["hod"]["name"]}, {F["hod"]["desig"]}, Department of {dept}')
105
+ r.replace(f"Panel Members Department of {dept}", f"Panel Members, Department of {dept}")
106
+ fa_txt = fa[0] if len(fa) == 1 else ", ".join(fa[:-1]) + " and " + fa[-1]
107
+ r.set_para("We register our immeasurable", f'We register our immeasurable thanks to our Faculty Advisor{"s" if len(fa) > 1 else ""}, {fa_txt}, Department of {dept}, SRM Institute of Science and Technology, for leading and helping us to complete our course.')
108
+ gtxt = ", and ".join(f'{g["name"]}, {g["desig"]}' for g in gs)
109
+ r.set_para("Our inexpressible respect", f'Our inexpressible respect and thanks to our guide{"s" if len(gs) > 1 else ""}, {gtxt}, Department of {dept}, SRM Institute of Science and Technology, for providing us with an opportunity to pursue our project under {"their" if len(gs) > 1 else "this"} mentorship, and for the freedom and support to explore the research topics of our interest.')
110
+ r.replace("<<Dept. Name>>", dept)
111
+ authors = next(e for e in r.els() if ptext(e).strip() == "Authors")
112
+ set_text(authors, f'{F["students"][0][0]} [{F["students"][0][1]}]')
113
+ r.trim_blank_before(authors, keep=1) # a longer author list would otherwise spill to a new page
114
+ r.start_new_page(r.find("ACKNOWLEDGEMENTS"))
115
+ r.tidy_paragraphs("ACKNOWLEDGEMENTS", "ABSTRACT")
116
+ last = next((p for p in r.all_paras() if ptext(p).rstrip().endswith("support and encouragement")), None)
117
+ if last is not None: # the template forgets this full stop
118
+ ts = list(last.iter(qn("w:t"))); ts[-1].text = ts[-1].text.rstrip() + "."
119
+ ppr = authors.find(qn("w:pPr"))
120
+ for x in ppr.findall(qn("w:jc")) + ppr.findall(qn("w:ind")) + ppr.findall(qn("w:tabs")):
121
+ ppr.remove(x)
122
+ ppr.append(parse_xml(f'<w:jc xmlns:w="{W}" w:val="right"/>'))
123
+ cur = authors
124
+ for n, g in F["students"][1:]:
125
+ cur = r.clone_after(authors, f"{n} [{g}]", after=cur)
126
+ r.bold_names("ACKNOWLEDGEMENTS", "ABSTRACT", F.get("bold_names", []) + [F["hod"]["name"]] + fa + [g["name"] for g in gs])
127
+
128
+
129
+ def build(template, content_path, facts_path, out):
130
+ F = json.load(open(facts_path, encoding="utf-8"))
131
+ c = json.load(open(content_path, encoding="utf-8"))
132
+ r = Report(template)
133
+ r.apply_cover_notes() # before filling, so cloned student and guide lines inherit the right size
134
+ cover(r, F); declaration(r, F); bonafide(r, F); acknowledgements(r, F)
135
+ r.strip_notes()
136
+ r.cut_from(r.find("ABSTRACT"))
137
+ r.prepare(c["chapters"] + c.get("appendices", []), c["refs"], c.get("img_dir", "."))
138
+ r.begin_front(); r.number_kept_sections()
139
+ r.add_nodes([{"t": "front", "text": "ABSTRACT"}] + [{"t": "p", "text": a} for a in c["abstract"]])
140
+ r.add_toc(); r.add_list_of_figures(); r.add_list_of_tables()
141
+ r.add_nodes([{"t": "front", "text": "ABBREVIATIONS"}, {"t": "abbrev", "items": c["abbreviations"]}])
142
+ r.begin_body(); r.add_nodes(c["chapters"]); r.add_references()
143
+ r.add_nodes(c.get("appendices", []))
144
+ print(r.save(out))
145
+
146
+
147
+ if __name__ == "__main__":
148
+ if len(sys.argv) != 5:
149
+ sys.exit(__doc__)
150
+ build(*sys.argv[1:5])
@@ -0,0 +1,171 @@
1
+ #!/usr/bin/env node
2
+ // Installs the srm-report skill for Claude Code, and sends a user's improvements back as a pull request.
3
+ const { execFileSync } = require("child_process");
4
+ const fs = require("fs");
5
+ const os = require("os");
6
+ const path = require("path");
7
+
8
+ const OWNER = "shauryaaojha";
9
+ const REPO = "srm-report";
10
+ const PKG_ROOT = path.join(__dirname, "..");
11
+ const SKILL_PATHS = ["SKILL.md", "references", "scripts", "assets"];
12
+ const DEFAULT_DIR = path.join(os.homedir(), ".claude", "skills", REPO);
13
+ // Reports and templates must never travel. Only text that improves the skill goes in a pull request.
14
+ const BLOCKED_FILE = /\.(docx?|dotx|pdf|pptx?|xlsx?|png|jpe?g|gif|zip|tgz)$/i;
15
+ const SKIP = /(^|[\\/])(__pycache__|\.git)([\\/]|$)|\.pyc$/;
16
+ const PERSONAL = [
17
+ [/\bRA\d{10,}\b/, "SRM registration number"],
18
+ [/[\w.+-]+@[\w-]+\.[\w.]{2,}/, "email address"],
19
+ [/(?<![\d.])[6-9]\d{9}(?![\d.])/, "phone number"],
20
+ ];
21
+
22
+ const HELP = `srm-report: SRM report skill for Claude Code
23
+
24
+ npx srm-report install [--dir PATH] [--force]
25
+ Copy the skill to ${DEFAULT_DIR}. --force replaces an existing copy.
26
+
27
+ npx srm-report contribute [--dir PATH]
28
+ Dry run. Compare your copy with the version you installed and print the diff. Sends nothing.
29
+
30
+ npx srm-report contribute --yes --title "What improved" [--body "Why"] [--dir PATH]
31
+ Push those changes and open a pull request. Needs the GitHub CLI, signed in.
32
+ Run this only after the person whose machine this is has read the diff and agreed.
33
+ `;
34
+
35
+ function parse(argv) {
36
+ const out = { _: [] };
37
+ for (let i = 0; i < argv.length; i++) {
38
+ const a = argv[i];
39
+ if (a === "--yes" || a === "--force" || a === "--help") out[a.slice(2)] = true;
40
+ else if (a.startsWith("--")) {
41
+ if (i + 1 >= argv.length) die(`${a} needs a value`);
42
+ out[a.slice(2)] = argv[++i];
43
+ } else out._.push(a);
44
+ }
45
+ return out;
46
+ }
47
+
48
+ function die(msg, code = 1) {
49
+ console.error(`srm-report: ${msg}`);
50
+ process.exit(code);
51
+ }
52
+
53
+ function run(cmd, args, cwd) {
54
+ return execFileSync(cmd, args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
55
+ }
56
+
57
+ function tryRun(cmd, args, cwd) {
58
+ try {
59
+ return run(cmd, args, cwd);
60
+ } catch {
61
+ return null;
62
+ }
63
+ }
64
+
65
+ function copySkill(from, to) {
66
+ for (const item of SKILL_PATHS) {
67
+ const src = path.join(from, item);
68
+ if (fs.existsSync(src)) fs.cpSync(src, path.join(to, item), { recursive: true, filter: (s) => !SKIP.test(s) });
69
+ }
70
+ }
71
+
72
+ function install(opts) {
73
+ const dir = path.resolve(opts.dir || DEFAULT_DIR);
74
+ if (fs.existsSync(path.join(dir, "SKILL.md")) && !opts.force) {
75
+ die(`${dir} already has this skill, and may hold your own improvements.\nSend those first with "npx srm-report contribute", or pass --force to replace it.`);
76
+ }
77
+ fs.mkdirSync(dir, { recursive: true });
78
+ copySkill(PKG_ROOT, dir);
79
+ const { version } = JSON.parse(fs.readFileSync(path.join(PKG_ROOT, "package.json"), "utf8"));
80
+ fs.writeFileSync(path.join(dir, "VERSION"), version + "\n");
81
+ console.log(`Installed srm-report ${version} to ${dir}`);
82
+ console.log("Claude Code picks it up in the next session.");
83
+ console.log('If you improve it while making a report, send it back: npx srm-report contribute');
84
+ }
85
+
86
+ function listFiles(dir, base = dir, out = []) {
87
+ for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
88
+ const p = path.join(dir, e.name);
89
+ if (SKIP.test(p)) continue;
90
+ if (e.isDirectory()) listFiles(p, base, out);
91
+ else out.push(path.relative(base, p));
92
+ }
93
+ return out;
94
+ }
95
+
96
+ function scanAdded(diff) {
97
+ const hits = [];
98
+ for (const line of diff.split("\n")) {
99
+ if (!line.startsWith("+") || line.startsWith("+++")) continue;
100
+ for (const [rx, label] of PERSONAL) if (rx.test(line)) hits.push(`${label}: ${line.slice(1).trim().slice(0, 100)}`);
101
+ }
102
+ return hits;
103
+ }
104
+
105
+ function contribute(opts) {
106
+ const dir = path.resolve(opts.dir || DEFAULT_DIR);
107
+ if (!fs.existsSync(path.join(dir, "SKILL.md"))) die(`no skill found at ${dir}`);
108
+ if (tryRun("gh", ["auth", "status"]) === null) die('the GitHub CLI is missing or signed out. Install gh and run "gh auth login".');
109
+ if (opts.yes && !opts.title) die('--yes needs --title "what improved"');
110
+
111
+ const blocked = SKILL_PATHS.flatMap((p) => {
112
+ const full = path.join(dir, p);
113
+ if (!fs.existsSync(full)) return [];
114
+ return fs.statSync(full).isDirectory() ? listFiles(full).map((f) => path.join(p, f)) : [p];
115
+ }).filter((f) => BLOCKED_FILE.test(f));
116
+ if (blocked.length) die(`these files can't go in a pull request. Reports and templates stay on your machine. Move them out of the skill folder:\n ${blocked.join("\n ")}`, 2);
117
+
118
+ const login = run("gh", ["api", "user", "--jq", ".login"]);
119
+ const own = login === OWNER;
120
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "srm-report-"));
121
+ const work = path.join(tmp, REPO);
122
+ try {
123
+ if (!own) run("gh", ["repo", "fork", `${OWNER}/${REPO}`, "--clone=false"]);
124
+ run("gh", ["repo", "clone", `${login}/${REPO}`, work]);
125
+ if (!own) tryRun("git", ["fetch", "upstream", "--tags"], work);
126
+
127
+ // Start from the version that was installed, so changes made upstream since then don't look like deletions.
128
+ const vf = path.join(dir, "VERSION");
129
+ const version = fs.existsSync(vf) ? fs.readFileSync(vf, "utf8").trim() : null;
130
+ const tag = version ? `v${version}` : null;
131
+ const hasTag = tag && tryRun("git", ["rev-parse", "--verify", "--quiet", `refs/tags/${tag}`], work) !== null;
132
+ const branch = `improve/${(opts.title || "changes").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40) || "changes"}-${Date.now().toString(36)}`;
133
+ run("git", hasTag ? ["checkout", "-b", branch, tag] : ["checkout", "-b", branch], work);
134
+
135
+ for (const p of SKILL_PATHS) fs.rmSync(path.join(work, p), { recursive: true, force: true });
136
+ copySkill(dir, work);
137
+ run("git", ["add", "-A", "--", ...SKILL_PATHS], work);
138
+
139
+ const stat = run("git", ["diff", "--cached", "--stat"], work);
140
+ if (!stat) {
141
+ console.log(`No changes: your copy matches ${hasTag ? tag : "the current skill"}.`);
142
+ return;
143
+ }
144
+ const diff = run("git", ["diff", "--cached", "-U2"], work);
145
+ const hits = scanAdded(diff);
146
+ if (hits.length) die(`personal details in your changes. Remove them and run this again. Nothing was sent.\n ${hits.join("\n ")}`, 2);
147
+
148
+ if (!opts.yes) {
149
+ console.log(diff);
150
+ console.log(`\n${stat}\n\nDRY RUN: nothing was sent. Read the whole diff, since names can't be detected automatically.`);
151
+ console.log('To send it, with the owner\'s agreement: npx srm-report contribute --yes --title "What improved" --body "Why"');
152
+ return;
153
+ }
154
+ run("git", ["commit", "-m", opts.title], work);
155
+ run("git", ["push", "-u", "origin", branch], work);
156
+ const head = own ? branch : `${login}:${branch}`;
157
+ const body = opts.body || `Improvements found while making a report.\n\n\`\`\`\n${stat}\n\`\`\``;
158
+ console.log(run("gh", ["pr", "create", "--repo", `${OWNER}/${REPO}`, "--base", "main", "--head", head, "--title", opts.title, "--body", body], work));
159
+ } catch (e) {
160
+ die(((e.stderr && e.stderr.toString()) || e.message).trim());
161
+ } finally {
162
+ fs.rmSync(tmp, { recursive: true, force: true });
163
+ }
164
+ }
165
+
166
+ const opts = parse(process.argv.slice(2));
167
+ const cmd = opts.help ? "help" : opts._[0] || "help";
168
+ if (cmd === "install") install(opts);
169
+ else if (cmd === "contribute") contribute(opts);
170
+ else if (cmd === "help") console.log(HELP);
171
+ else die(`unknown command "${cmd}"\n\n${HELP}`);
package/package.json CHANGED
@@ -1,6 +1,36 @@
1
1
  {
2
2
  "name": "srm-report",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.0.0",
4
+ "description": "Claude Code skill that builds a print-ready SRM Institute report (.docx and .pdf) from the template you attach.",
5
+ "bin": {
6
+ "srm-report": "bin/srm-report.js"
7
+ },
8
+ "files": [
9
+ "bin",
10
+ "SKILL.md",
11
+ "references",
12
+ "scripts/*.py",
13
+ "scripts/*.ps1",
14
+ "assets",
15
+ "CONTRIBUTING.md"
16
+ ],
17
+ "keywords": [
18
+ "claude-code",
19
+ "claude-skill",
20
+ "srm",
21
+ "srmist",
22
+ "report",
23
+ "docx"
24
+ ],
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "git+https://github.com/shauryaaojha/srm-report.git"
28
+ },
29
+ "homepage": "https://github.com/shauryaaojha/srm-report#readme",
30
+ "bugs": "https://github.com/shauryaaojha/srm-report/issues",
31
+ "author": "shauryaaojha",
32
+ "license": "MIT",
33
+ "engines": {
34
+ "node": ">=18"
35
+ }
36
+ }
@@ -0,0 +1,40 @@
1
+ # Writing the content
2
+
3
+ The report goes to faculty who sign it, and the user asks directly whether everything is real. So honesty rules come first.
4
+
5
+ ## Everything real
6
+
7
+ - **Results:** re-run the tests, evaluations and benchmarks, then quote those numbers with their denominators ("0 false passes out of 23"). Don't quote from old notes or memory.
8
+ - **Not done means pending.** Write "pending" or "planned", and tell the user. Never write a finished-sounding sentence about work that hasn't happened.
9
+ - **Baselines:** if a comparison number is an estimate, say so in the sentence.
10
+ - **Screenshots** show the real system on fixture data, from a separate instance.
11
+ - **Publications:** never imply a submission or acceptance that hasn't happened.
12
+
13
+ ## References
14
+
15
+ The template asks for at least 15, but every one must be real and actually relevant.
16
+
17
+ 1. Search for the paper or standard. Don't write a reference from memory.
18
+ 2. Open the primary source: publisher page, arXiv, DOI, the official PDF. Check authors, title, venue, year and pages.
19
+ 3. Check that the sentence citing it matches what the source says. For laws and regulations, quote word for word from the official PDF, and keep the date and notification number.
20
+ 4. List web sources with an accessed date.
21
+ 5. If you can't verify one, remove it and tell the user.
22
+
23
+ `Report.prepare()` numbers citations by first appearance and stops the build if a reference is never cited or a citation has no reference. So add references as `key: text` and cite them as `[@key]`, and never type numbers by hand.
24
+
25
+ ## Writing style
26
+
27
+ - Plain, specific prose in "we". Name the real component, the real number and the real file.
28
+ - Keep paragraphs under 12 lines, the template's limit; the checker warns on long ones.
29
+ - Introduce every figure and table in the text before it appears, using `{fig:id}` and `{tab:id}`.
30
+ - Use tables for lists of requirements, comparisons and results. Keep each short enough for one page; split a long one into two numbered tables.
31
+ - Use the `humanizer` skill's patterns as a scan on the prose before handing over: inflated claims, staged contrasts, stock AI words.
32
+ - Don't pad to reach the page minimum. If it's short, add real substance: sprint documents, test tables, failure analysis.
33
+
34
+ ## Figures
35
+
36
+ White background, Times New Roman, print-friendly colours that survive greyscale, and no chart titles repeating the caption. Check images by looking at them after placement. Give every figure a short caption that says what it shows.
37
+
38
+ ## Where things come from
39
+
40
+ Before asking the user, look through the repo, its README and PRD, test output and earlier reports. Use real source code for appendix listings, with the parts that carry the project's main claims chosen first. Put a note before them saying where the full code lives.
@@ -0,0 +1,38 @@
1
+ # Report families seen so far
2
+
3
+ Use this for the shape of a report only. Exact wording, page order and sizes come from the attached template.
4
+
5
+ ## Project report (21CSE734P, 21CSP302L)
6
+
7
+ Fully tested recipe: `assets/examples/project_21cse734p.py`.
8
+
9
+ 1. **Introduction:** introduction, problem statement, motivation, Sustainable Development Goal
10
+ 2. **Literature Survey:** overview, existing models, gaps, objectives, product backlog, plan of action
11
+ 3. **Sprint Planning and Execution:** for each sprint: objectives with user stories, functional document, architecture document, outcome and result analysis, retrospective
12
+ 4. **Results and Discussions**
13
+ 5. **Conclusion and Future Enhancement**
14
+
15
+ Then References, Appendix A Coding, B Conference Publication, C Journal Publication, D Plagiarism Report. Drop and re-letter B and C if there are no publications and the user agrees.
16
+
17
+ ## Community Connect (21GNP301L)
18
+
19
+ The template comes with a separate rubric, so read it as well. No recipe yet: copy the project recipe and rewrite the fill steps.
20
+
21
+ - Cover
22
+ - Bonafide with Faculty Advisor, Panel Member, Academic Advisor and HoD, worded "under NAME OF THE ORGANIZATION"
23
+ - Completion certificate from the organisation (the user supplies it)
24
+ - Log sheet
25
+ - Objectives: one paragraph of at most 300 words covering the services and the reason for choosing the organisation
26
+ - Table of Contents
27
+ - Ch 1 Introduction of the organisation, at least 2 pages
28
+ - Ch 2 Week-wise report
29
+ - Ch 3 Tangible learning, at least 2 pages
30
+ - Ch 4 Student's contribution, 2 pages per student with their own photos (mandatory)
31
+ - Ch 5 Conclusion
32
+ - Binding instructions: follow them, and don't print them in the report
33
+
34
+ Photos need matching dates and sessions. Ask for them rather than reusing whatever is at hand.
35
+
36
+ ## Course case studies and small projects (APP, OS, DBMS, DIP)
37
+
38
+ These vary the most. One OS case study template uses 14pt body text on a Letter page. Take the page size, margins and body size from `sections.txt` and `structure.txt`, and pass them through `Report(template, style={...})`. These templates are often short, so most of their pages may be kept and filled in place, with no cut at all.