Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/pr-preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ jobs:
ref: ${{ github.event.pull_request.head.sha }}
path: pr
submodules: recursive
fetch-depth: 0
- name: Install Node.js dependencies for PR
working-directory: pr
run: "[[ -f package-lock.json || -f npm-shrinkwrap.json ]] && npm ci || true"
Expand All @@ -93,6 +94,14 @@ jobs:
--baseURL "${{ steps.base_url.outputs.base_url }}/" \
--destination "${{ github.workspace }}/public/pr-${{ github.event.number }}"

- name: Check internal links
working-directory: pr
env:
SITE_BASE_URL: ${{ steps.base_url.outputs.base_url }}/
PUBLIC_DIR: ${{ github.workspace }}/public/pr-${{ github.event.number }}
LINK_CHECK_BASE_REF: origin/${{ github.base_ref }}
run: node scripts/check-changed-links.mjs

- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/production-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ jobs:
uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0
- name: Setup Pages
id: pages
uses: actions/configure-pages@v5
Expand All @@ -63,6 +64,11 @@ jobs:
--minify \
--baseURL "${{ steps.pages.outputs.base_url }}/"

- name: Check internal links
env:
SITE_BASE_URL: ${{ steps.pages.outputs.base_url }}/
run: node scripts/check-changed-links.mjs

Comment on lines +67 to +71
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
Expand Down
12 changes: 9 additions & 3 deletions config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -316,22 +316,28 @@ pluralizelisttitles = false
weight = 2
parent = "menu.programs_activities"

[[menu.main]]
name = "RISE"
url = "/rise/rise"
weight = 3
parent = "menu.programs_activities"

[[menu.main]]
name = "Travel Support and Childcare Assustance (CAPS)"
url = "/activities/capsmain"
weight = 3
weight = 4
parent = "menu.programs_activities"

[[menu.main]]
name = "Webinars"
url = "/activities/webinarsmain"
weight = 4
weight = 5
parent = "menu.programs_activities"

[[menu.main]]
name = "Summer/Winter Schools"
url = "/activities/schools"
weight = 5
weight = 6
parent = "menu.programs_activities"

[[menu.main]]
Expand Down
2 changes: 1 addition & 1 deletion content/activities/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ ACM SIGSOFT sponsors many activities and events.
Here is a non-exhaustive list of them.

- [CARES]({{< ref "/cares/SIGSOFT_CARES.md" >}} "cares")
- [RISE: Research Alliance for Industry and Academia in Software Engineering]({{< ref "/rise/RISE.md" >}} "rise")
- [SIGSOFT Travel Support and Childcare Assistance at Conferences - CAPS]({{< ref "CAPSMAIN.md" >}} "caps")
- [WEBINARS]({{< ref "webinarsmain.md" >}} "webinars")
- [Summer/Winter Schools]({{< ref "schools.md" >}} "schools")
Expand All @@ -21,4 +22,3 @@ Here is a list of new initiatives that SIGSOFT is exploring, and the correspondi
- SIGSOFT ACM Digital Library liaisons: Xiaoning Du and Tao Zhang. The liaisons will ensure that information about SIGSOFT (co)-located events has been correctly captured in ACM Digital Library.
- SIGSOFT Conference Program Committee Member Recognition Coordinators: Sylvain Hallé and Yepang Liu. The coordinators will create and mail personalized digital certificates for program committee roles of SIGSOFT (co)-sponsored conferences.
- Research Highlights coordinator: Silvia Abrahão. The coordinator will work with program chairs of SIGSOFT (co)-sponsored conferences to identify most promising papers for possible publication in Communications of the ACM.

51 changes: 51 additions & 0 deletions content/rise/RISE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
+++
title = "RISE: Research Alliance for Industry and Academia in Software Engineering"
description = "An ACM SIGSOFT Task Force on AI and Software Engineering in India"
keywords = ["RISE", "AI and Software Engineering", "India", "Task Force"]
+++

RISE (Research Alliance for Industry and Academia in Software Engineering) is an ACM SIGSOFT Task Force and a research-driven initiative that fosters engagement between Indian software industry, government, and the global software engineering research community. Its current focus is on AI and Software Engineering in India.

The RISE website is available at [https://rise-sigsoft.github.io/](https://rise-sigsoft.github.io/).

## Purpose

India hosts one of the world's largest software engineering ecosystems, employing over five million software professionals across diverse domains and cities, with major global R&D centres operated by Microsoft, IBM, Mercedes-Benz, Shell, SAP, and others. Yet despite accounting for around 16% of the global software workforce, India's representation in top-tier international software engineering venues such as ICSE, FSE, and ASE remains below 2%.

As AI-driven software development accelerates, India's industry is witnessing large-scale adoption of generative and predictive AI in everyday software work. This presents an opportunity to document, understand, and shape how AI is transforming software engineering practice in real-world industrial contexts.

Operating under ACM SIGSOFT, RISE works to surface real-world AI and software engineering challenges from Indian industry, mobilise academic researchers to engage with them, and build structured linkages with industry bodies such as NASSCOM and adjacent platforms across industry and government.

## Mission

The Task Force is organised around four working pillars.

**Industry Engagement and Challenge Discovery.** Reach out to major software R&D centres, software powerhouses, and startups across India to identify and document real-world AI-driven software engineering practices, challenges, and emerging solutions through structured meetings and industry events.

**Industry Body Integration.** Build formal collaboration between ACM SIGSOFT and Indian software industry bodies such as NASSCOM, with the goal of creating dedicated threads on Software Engineering and Research toward Indian and global software engineering research.

**Co-designed Research Challenges.** Mobilise academic researchers to engage with industry-defined problems through co-created research challenge calls, pilot projects, and a curated catalogue of challenges accessible to the SIGSOFT community.

**Industry Consortium and Sustainability.** Build an industry consortium across associations, R&D centres, and product firms to sustain RISE activities, with the aim of industry-led continuity integrated into ISEC and adjacent forums over time.

## Ecosystem

RISE works across a broad set of stakeholders that shape software engineering practice and research in India. These span industry and R&D centres, industry bodies and associations, government and standards bodies, and academic and industry conference venues. A working stakeholder map is maintained by the Task Force and will be expanded as it progresses.

## Leadership

RISE is chaired by Sridhar Chimalakonda, Associate Professor and Head of the Department of Computer Science and Engineering at IIT Tirupati, Adjunct Associate Professor at the University of Waterloo, and Digital Learning Co-Chair of ACM SIGSOFT. Co-leads from industry and academia will be identified and confirmed in due course.

RISE is established as an ACM SIGSOFT Task Force, with annual reporting to the SIGSOFT Executive Committee.

## Get Involved

RISE welcomes researchers, practitioners, students, and chapter representatives who would like to take part. You can express your interest in participating through the form on the RISE website:

[Visit the RISE website](https://rise-sigsoft.github.io/)

## Contact

Sridhar Chimalakonda, Chair, RISE Task Force
Department of Computer Science and Engineering, IIT Tirupati
Email: [ch@iittp.ac.in](mailto:ch@iittp.ac.in)
181 changes: 181 additions & 0 deletions scripts/check-changed-links.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
#!/usr/bin/env node

import fs from "node:fs";
import path from "node:path";
import { execFileSync } from "node:child_process";

const publicRoot = path.resolve(process.env.PUBLIC_DIR || "public");
const siteBase = new URL(process.env.SITE_BASE_URL || "https://www2.sigsoft.org/");
const siteBasePath = normalizePath(siteBase.pathname);
const ignoredProtocols = new Set(["mailto:", "tel:", "javascript:", "data:", "blob:"]);

const changedFiles = process.argv.slice(2).filter((file) => fs.existsSync(file));
const filesToCheck = changedFiles.length > 0 ? changedFiles : gitChangedFiles();
const failures = [];

for (const file of filesToCheck) {
if (!/\.(md|markdown|html|toml|yaml|yml)$/i.test(file)) continue;

const source = fs.readFileSync(file, "utf8");
for (const link of extractLinks(source)) {
checkLink(file, link);
}
}

if (failures.length > 0) {
console.error(`Found ${failures.length} broken link(s) in changed files:`);
for (const failure of failures) console.error(`- ${failure}`);
process.exit(1);
}

console.log(`Checked links in ${filesToCheck.length} changed file(s); no new broken internal links found.`);

function checkLink(sourceFile, rawLink) {
const link = decodeHtml(rawLink.trim());
if (!link || link.startsWith("#") || link.startsWith("//")) return;

if (link.includes("{{<") || link.includes("{{%")) return;

const refTarget = parseHugoRef(link);
if (refTarget) {
checkContentPath(sourceFile, link, refTarget);
return;
}

if (/\.md$/i.test(link)) {
checkContentPath(sourceFile, link, link);
return;
}

const internalPath = toInternalPath(link);
if (!internalPath) return;

const target = resolvePublicPath(internalPath);
if (!fs.existsSync(target)) {
failures.push(`${sourceFile}: broken internal link ${link} -> ${slash(path.relative(publicRoot, target))}`);
}
}

function checkContentPath(sourceFile, link, target) {
const contentPath = target.startsWith("/")
? path.resolve("content", target.replace(/^\/+/, ""))
: path.resolve(path.dirname(sourceFile), target);

if (!fs.existsSync(contentPath)) {
failures.push(`${sourceFile}: broken Hugo/content ref ${link} -> ${slash(path.relative(process.cwd(), contentPath))}`);
}
}

function extractLinks(source) {
const links = new Set();
const patterns = [
/\[[^\]]*]\(([^)\s]+)(?:\s+["'][^"']*["'])?\)/g,
/\b(?:href|src|action)=["']([^"']+)["']/gi,
/^\s*url\s*=\s*["']([^"']+)["']/gim,
/\{\{<\s*ref\s+["']([^"']+)["']\s*>}}/g,
/\{\{%\s*ref\s+["']([^"']+)["']\s*%}}/g,
];

for (const pattern of patterns) {
let match;
while ((match = pattern.exec(source)) !== null) {
links.add(match[1]);
}
}

return links;
}

function parseHugoRef(link) {
const match = link.match(/^\{\{[<%]\s*ref\s+["']([^"']+)["']\s*[>%]}}$/);
return match ? match[1] : null;
}

function toInternalPath(link) {
let value = link;

try {
const url = new URL(link);
if (ignoredProtocols.has(url.protocol)) return null;
if (url.origin !== siteBase.origin) return null;
value = stripSiteBasePath(url.pathname);
} catch {
if (/^[a-z][a-z0-9+.-]*:/i.test(link)) return null;
if (!link.startsWith("/")) return null;
value = link;
}

value = value.split("#")[0].split("?")[0];
return value || "/";
}

function resolvePublicPath(urlPath) {
const decoded = safeDecode(urlPath).replace(/^\/+/, "");
const candidate = path.join(publicRoot, decoded);

if (path.extname(candidate)) return candidate;
if (urlPath.endsWith("/")) return path.join(candidate, "index.html");

const asDirectoryIndex = path.join(candidate, "index.html");
if (fs.existsSync(asDirectoryIndex)) return asDirectoryIndex;
return `${candidate}.html`;
}

function gitChangedFiles() {
const baseRef = process.env.LINK_CHECK_BASE_REF;
const args = baseRef ? ["diff", "--name-only", `${baseRef}...HEAD`] : ["diff", "--name-only", "HEAD^", "HEAD"];

try {
const files = execFileSync("git", args, { encoding: "utf8" })
.split(/\r?\n/)
.filter(Boolean);

if (files.length === 0) {
console.error(`No changed files found from: git ${args.join(" ")}`);
console.error("Pass files explicitly or ensure the checkout has enough git history.");
process.exit(1);
}

return files;
} catch {
console.error(`Unable to determine changed files from: git ${args.join(" ")}`);
console.error("Pass files explicitly or ensure the checkout has enough git history.");
process.exit(1);
}
Comment on lines +124 to +144
}

function stripSiteBasePath(urlPath) {
const normalized = normalizePath(urlPath);
if (siteBasePath !== "/" && normalized.startsWith(siteBasePath + "/")) {
return normalized.slice(siteBasePath.length);
}
return normalized;
}

function normalizePath(value) {
const normalized = value.startsWith("/") ? value : `/${value}`;
return normalized.length > 1 ? normalized.replace(/\/+$/, "") : normalized;
}

function safeDecode(value) {
try {
return decodeURIComponent(value);
} catch {
return value;
}
}

function decodeHtml(value) {
return value
.replaceAll("&amp;", "&")
.replaceAll("&quot;", '"')
.replaceAll("&#34;", '"')
.replaceAll("&#39;", "'")
.replaceAll("&apos;", "'")
.replaceAll("&lt;", "<")
.replaceAll("&gt;", ">");
}

function slash(value) {
return value.split(path.sep).join("/");
}
Loading