docs: add xia recon report and port plan

Captures the analysis behind the linkyee-inspired implementation: source
anatomy, dependency matrix, decision matrix (stack, plugins, theme, icons,
deploy), and four phase files used to drive the build.
This commit is contained in:
tiennm99 committed 2026-04-30 22:05:12 +07:00
1 parent a90cb8126a
commit 17cbbeacb2
8 files changed
+1086

No files matched your search

@@ -0,0 +1,146 @@
# Phase 01 — Scaffold & config schema
## Context links
- [plan.md](plan.md)
- [recon report](../reports/xia-260430-2135-linkyee-recon.md)
- linkyee config reference: /tmp/linkyee-src/config.yml
## Overview
- **Priority:** P0 (gates everything)
- **Status:** ✅ completed
- **Description:** Establish project layout and config schema. Single source of truth for all page content.
## Key insights
- linkyee's nested `- link: { ... }` wrapper is verbose YAML; flatten to `- { ... }`.
- Splitting `profile`, `links`, `socials` into one file keeps editing fast.
- JSON would be zero-dep; YAML is friendlier for hand editing. **Pick YAML** + add `js-yaml` as the only npm dep.
- `repo:` field on a link is the trigger that activates star + last-commit lookup.
## Requirements
**Functional:**
- One YAML file, `config.yaml`, holds: profile, links[], socials[], meta (title, description, OG image), footer.
- `links[]` items support `text`, `url`, `icon`, `target`, optional `repo` (`owner/name`) for live data.
- `socials[]` items support `icon`, `url`, `title`.
**Non-functional:**
- Schema is documented inline (comments) and in this phase.
- Schema is small enough to keep in one file under 200 lines.
## Architecture
```
iammiti99/
├── config.yaml # ← single source of truth
├── src/
│ ├── template.html # raw HTML template (Phase 2)
│ ├── styles.css # (Phase 2)
│ └── scripts.js # (Phase 2; may stay empty)
├── build.js # (Phase 3)
├── package.json # (Phase 3)
├── _site/ # build output (.gitignore)
├── .github/workflows/
│ └── deploy.yml # (Phase 4)
├── README.md
├── LICENSE # (existing) Apache-2.0
└── NOTICE # credit linkyee inspiration
```
## Related code files
**Create:**
- `config.yaml`
- `.gitignore` additions: `_site/`, `node_modules/`
- `NOTICE`
**Modify:** none
**Delete:** none
## Config schema
```yaml
# Site meta
site:
title: "Tien — links"
description: "Tien Nguyen Minh — engineer / builder / writer"
url: "https://iammiti99.github.io" # for OG tags
lang: "en"
theme_color_light: "#f7f7f7"
theme_color_dark: "#1b1b1e"
# Profile
profile:
name: "@miti99"
tagline: "Engineer building things on the web."
avatar: "./images/avatar.jpg"
# Optional analytics (omit to disable)
analytics:
google_analytics_id: ""
# Primary link list (rendered as buttons in order)
links:
- text: "Tech blog"
url: "https://blog.example.com"
icon: "fa-solid fa-pen-nib"
target: "_blank"
- text: "iammiti99"
url: "https://github.com/tiennm99/iammiti99"
icon: "fa-brands fa-github"
target: "_blank"
repo: "tiennm99/iammiti99" # ← triggers stars + last-commit decoration
# Social icon row (small, below links)
socials:
- icon: "fa-brands fa-github"
url: "https://github.com/tiennm99"
title: "GitHub"
- icon: "fa-solid fa-envelope"
url: "mailto:minhtienit99@gmail.com"
title: "Email"
# Footer
footer:
text: "Thanks for stopping by."
copyright: "© 2026 Tien Nguyen Minh"
```
## Implementation steps
1. Write `config.yaml` at repo root with the schema above; populate with placeholder values from `README.md` user.
2. Append `_site/`, `node_modules/`, `.DS_Store` to `.gitignore`.
3. Create `NOTICE` file with linkyee inspiration credit:
```
This project is inspired by linkyee (https://github.com/ZhgChgLi/linkyee, MIT).
No code is copied; only the architectural idea (config-driven link page on GitHub Pages).
```
4. Create empty `src/`, `_site/` directories (add `.gitkeep` to `src/` if empty so it commits).
## Todo
- [ ] Write `config.yaml`
- [ ] Update `.gitignore`
- [ ] Add `NOTICE`
- [ ] Create `src/` directory
## Success criteria
- `config.yaml` parses with `js-yaml` (verify with `node -e "console.log(require('js-yaml').load(require('fs').readFileSync('config.yaml','utf8')))"`)
- `.gitignore` excludes build output
## Risks
- Over-engineering schema with fields we won't use. Mitigation: only add a field once Phase 2 needs it.
## Security
- `config.yaml` is public — never put secrets here.
- `analytics.google_analytics_id` is non-secret (public client-side ID).
## Next
→ Phase 2: design HTML template and CSS.
@@ -0,0 +1,123 @@
# Phase 02 — Page design (HTML + CSS)
## Context links
- [plan.md](plan.md)
- [phase-01-scaffold-and-config.md](phase-01-scaffold-and-config.md)
- linkyee theme reference (do not copy): /tmp/linkyee-src/themes/default/
## Overview
- **Priority:** P0
- **Status:** ✅ completed
- **Description:** Fresh, minimalist HTML template + CSS. Mobile-first. Light + dark via `prefers-color-scheme`.
## Key insights
- Fresh design — Q3 ruled out reusing linkyee's CSS. **Do not import or copy linkyee files.**
- Single page, no JS framework. Template uses `${var}` placeholders that the build script replaces.
- FA via CDN (Q4) — pin a specific FA 6 version for cache stability.
- linkyee's HTML had solid SEO/social meta — replicate the *categories* (og, twitter, theme-color, favicons) but write our own tags.
## Requirements
**Functional:**
- Render: profile (avatar, name, tagline), link buttons in order, social icon row, footer.
- Each link button shows icon + text; if from a `repo:`-flagged item, also shows ` ★ {stars} · updated {N}d ago`.
- Light + dark mode via CSS media query, no JS toggle.
- Page weight target: <30 KB HTML+CSS+JS, FA loaded from CDN.
**Non-functional:**
- Lighthouse ≥95 (perf, a11y, best-practices, SEO).
- No layout shift; proper `width`/`height` on `<img>`.
## Architecture
Template structure:
```
<html>
<head>
meta (charset, viewport, description, og:*, twitter:*)
favicons + theme-color
FA CDN <link>
inline <style> (or external styles.css — small enough to inline)
</head>
<body>
<main class="card">
<img class="avatar" src="${avatar}" />
<h1>${name}</h1>
<p class="tagline">${tagline}</p>
<ul class="links">
${links_html} <!-- generated by build.js -->
</ul>
<nav class="socials">
${socials_html} <!-- generated by build.js -->
</nav>
<footer>${footer_text}<small>${copyright}</small></footer>
</main>
<!-- optional GA snippet, gated by build.js -->
</body>
</html>
```
## Related code files
**Create:**
- `src/template.html`
- `src/styles.css` (or inline into template — decide during impl based on size)
- `src/scripts.js` (likely empty; create only if needed)
- `src/images/avatar.jpg` (placeholder; user replaces)
## Implementation steps
1. Write `src/template.html` with `${...}` placeholders matching `config.yaml` keys:
- `${site.title}`, `${site.description}`, `${site.url}`, `${site.lang}`
- `${profile.name}`, `${profile.tagline}`, `${profile.avatar}`
- `${links_html}` (build.js generates the `<li><a>...` strings)
- `${socials_html}` (build.js generates the `<a class="icon">...` strings)
- `${footer.text}`, `${footer.copyright}`
- `${last_modified}` (ISO-8601, set by build.js)
2. Write `src/styles.css`:
- Reset (small: `*,*::before,*::after{box-sizing:border-box}`)
- `:root` custom properties for colors (light)
- `@media (prefers-color-scheme: dark) :root { ... }` for dark colors
- `.card` centered, `max-width: 480px`, padding, border-radius
- `.avatar` round, 120×120, `loading="eager"`
- `.links li a` button style with hover
- `.socials a` icon-only inline-flex
- Responsive: works <320px wide
3. Pick FA 6 via CDN: `<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css" integrity="..." crossorigin="anonymous">` (with SRI hash).
4. Add favicon set under `src/favicons/` (user supplies; placeholder via favicon.io or similar).
5. Hand-test in browser (`python3 -m http.server` from `_site/` once Phase 3 generates it).
## Todo
- [ ] `src/template.html`
- [ ] `src/styles.css`
- [ ] Pick FA 6 CDN URL with SRI
- [ ] Avatar placeholder
- [ ] Favicons (or skip until v2)
## Success criteria
- Template has every `${...}` placeholder needed by `config.yaml` schema.
- Styles validate (no broken selectors).
- Dark mode visually distinct (verified via DevTools color-scheme override).
## Risks
- Inline-style temptation in template will fight CSS. Mitigation: enforce CSS-only styling.
- FA CDN outage = missing icons. Mitigation: low risk for personal site; if it bites, vendor a subset later.
## Security
- FA CDN: include `integrity=` SRI hash to pin script integrity.
- Avatar should be <100 KB and free of EXIF GPS metadata.
## Next
→ Phase 3: build script that fills in `${...}` and generates the link/social HTML.
@@ -0,0 +1,185 @@
# Phase 03 — Build script + GitHub data
## Context links
- [plan.md](plan.md)
- [phase-01-scaffold-and-config.md](phase-01-scaffold-and-config.md)
- [phase-02-page-design.md](phase-02-page-design.md)
- linkyee scaffolder reference: /tmp/linkyee-src/scaffold.rb (do not copy)
## Overview
- **Priority:** P0
- **Status:** ✅ completed
- **Description:** Node.js script that reads `config.yaml`, fetches GitHub data for every `repo:`-flagged link, renders `src/template.html` to `_site/index.html`, copies static assets.
## Key insights
- Use Node 20+ built-in `fetch` — no `node-fetch` dep.
- Only npm dep: `js-yaml`. Keeps `package.json` lean.
- GitHub API: `GET /repos/{owner}/{repo}` returns `stargazers_count` AND `pushed_at` in one call. **One request per repo.**
- In Actions, set `Authorization: Bearer ${GITHUB_TOKEN}` header → 1000 req/hour, no rate-limit risk.
- Fail soft: if API call errors, render link without star/date decoration; do not break the build.
- Template substitution = `string.replaceAll('${key}', value)` (Node 15+). No template engine needed.
## Requirements
**Functional:**
- Read `config.yaml` from repo root.
- For each link with `repo:`, fetch `stargazers_count` + `pushed_at` from GH API.
- Render link HTML: `<li><a href="${url}" target="${target}"><i class="${icon}"></i><span>${text}</span><small>★ ${stars} · ${days}d ago</small></a></li>` for repo links; minimal version otherwise.
- Render socials HTML.
- Substitute placeholders in `src/template.html`.
- Write `_site/index.html`.
- Copy `src/styles.css`, `src/scripts.js`, `src/images/`, `src/favicons/` to `_site/`.
**Non-functional:**
- Build completes in <10 s for ~10 repos.
- No network calls if no `repo:` flags present.
- Exit code reflects failure (non-zero) only on malformed config / template, not on API hiccups.
## Architecture
```
build.js (Node 20)
├── parseConfig() # load + validate config.yaml
├── fetchRepoData() # parallel fetch for all repos with one Bearer token
│ └── fail-soft → returns null on error, build continues
├── renderLinks() # produces ${links_html} string
├── renderSocials() # produces ${socials_html} string
├── renderTemplate() # string.replaceAll for every ${key}
├── copyAssets() # cp -r src/{styles.css,scripts.js,images,favicons} _site/
└── main() # orchestrates, writes _site/index.html
```
## Related code files
**Create:**
- `build.js`
- `package.json`
- `package-lock.json` (auto-generated by `npm install`)
**Modify:**
- `.gitignore` (already includes `_site/`, `node_modules/` from Phase 1)
## Implementation steps
1. `npm init -y` → edit `package.json`:
- `"type": "module"` (use ESM)
- `"engines": { "node": ">=20" }`
- `"scripts": { "build": "node build.js" }`
- `"dependencies": { "js-yaml": "^4.1.0" }`
2. `npm install js-yaml` → commit `package-lock.json`.
3. Write `build.js`:
```js
import { readFile, writeFile, mkdir, cp } from 'node:fs/promises';
import { existsSync } from 'node:fs';
import yaml from 'js-yaml';
const SITE = '_site';
const TOKEN = process.env.GITHUB_TOKEN;
const config = yaml.load(await readFile('config.yaml', 'utf8'));
const template = await readFile('src/template.html', 'utf8');
async function fetchRepo(slug) {
try {
const r = await fetch(`https://api.github.com/repos/${slug}`, {
headers: TOKEN ? { Authorization: `Bearer ${TOKEN}`, 'User-Agent': 'iammiti99-build' } : { 'User-Agent': 'iammiti99-build' },
});
if (!r.ok) return null;
const j = await r.json();
return { stars: j.stargazers_count, pushed: j.pushed_at };
} catch { return null; }
}
const repoData = Object.fromEntries(
await Promise.all(
(config.links || [])
.filter(l => l.repo)
.map(async l => [l.repo, await fetchRepo(l.repo)])
)
);
const daysSince = iso => Math.floor((Date.now() - new Date(iso)) / 86_400_000);
const linksHtml = (config.links || []).map(l => {
const data = l.repo ? repoData[l.repo] : null;
const decoration = data
? ` <small>★ ${data.stars} · ${daysSince(data.pushed)}d ago</small>`
: '';
return `<li><a href="${l.url}" target="${l.target || '_self'}" rel="noopener"><i class="${l.icon}"></i><span>${l.text}</span>${decoration}</a></li>`;
}).join('\n');
const socialsHtml = (config.socials || []).map(s =>
`<a href="${s.url}" title="${s.title}" rel="noopener" target="_blank"><i class="${s.icon}"></i></a>`
).join('\n');
const replacements = {
'${site.title}': config.site.title,
'${site.description}': config.site.description,
'${site.url}': config.site.url,
'${site.lang}': config.site.lang || 'en',
'${profile.name}': config.profile.name,
'${profile.tagline}': config.profile.tagline,
'${profile.avatar}': config.profile.avatar,
'${links_html}': linksHtml,
'${socials_html}': socialsHtml,
'${footer.text}': config.footer.text,
'${footer.copyright}': config.footer.copyright,
'${last_modified}': new Date().toISOString(),
};
let out = template;
for (const [k, v] of Object.entries(replacements)) {
out = out.replaceAll(k, v);
}
if (!existsSync(SITE)) await mkdir(SITE, { recursive: true });
await writeFile(`${SITE}/index.html`, out);
for (const dir of ['styles.css', 'scripts.js', 'images', 'favicons']) {
const src = `src/${dir}`;
if (existsSync(src)) await cp(src, `${SITE}/${dir}`, { recursive: true });
}
console.log(`Built ${SITE}/index.html (${Object.keys(repoData).length} repos)`);
```
4. Local test:
```bash
node build.js && python3 -m http.server -d _site 8080
```
Visit `http://localhost:8080` and verify rendering.
5. (Optional) Add minimal validation: throw if `config.site` or `config.profile` missing.
## Todo
- [ ] `package.json`
- [ ] Install `js-yaml`
- [ ] `build.js`
- [ ] Local test passes
- [ ] Verify star + last-commit decoration appears for at least one `repo:` link
- [ ] Verify build succeeds with no `repo:` links (zero API calls)
## Success criteria
- `node build.js` exits 0 with `config.yaml` from Phase 1.
- `_site/index.html` is non-empty, contains substituted profile data, every `${...}` placeholder is gone.
- Repo links show `★ N · Md ago`.
- Build completes in <10 s with ~5 repo links.
## Risks
- Naive `string.replaceAll` is XSS-prone if config values contain `<script>`. Mitigation: this is single-user content the user controls — accept the risk; add HTML-escape only if multi-user.
- Native `fetch` requires Node 20+. Mitigation: pin in `engines` and Actions matrix.
- `pushed_at` reflects any push (incl. branches), not just default branch commits — close enough for "last updated" UX.
## Security
- Use `rel="noopener"` on all `target="_blank"` links (prevents `window.opener` leak).
- Do not log `GITHUB_TOKEN` value, only its presence.
- API errors are swallowed silently — log them at debug level only.
## Next
→ Phase 4: GitHub Actions workflow that runs this build and deploys to Pages.
@@ -0,0 +1,121 @@
# Phase 04 — Deploy pipeline (GitHub Actions → Pages)
## Context links
- [plan.md](plan.md)
- [phase-03-build-and-data.md](phase-03-build-and-data.md)
- linkyee deploy reference (legacy pattern, do not copy): /tmp/linkyee-src/deploy.sh + .github/workflows/build.yml
## Overview
- **Priority:** P0
- **Status:** ✅ completed
- **Description:** Modern GitHub Pages deployment via Actions artifact. No `gh-pages` branch.
## Key insights
- linkyee's `deploy.sh` is legacy: force-pushes to `gh-pages`. **Skip entirely.**
- Modern path: `actions/upload-pages-artifact@v3` + `actions/deploy-pages@v4`.
- Workflow needs `permissions: { pages: write, id-token: write, contents: read }`.
- `GITHUB_TOKEN` is auto-injected; export it to the build step's env.
- Triggers (3): `push` to `main`, daily `schedule` cron, manual `workflow_dispatch`.
## Requirements
**Functional:**
- On push to `main`: build → deploy.
- On daily cron `0 0 * * *`: rebuild (refreshes star + last-commit) → deploy.
- On manual dispatch: same.
- Build uses Node 20.
**Non-functional:**
- Concurrency: in-flight deploys cancel earlier ones (`concurrency: pages` + `cancel-in-progress: true`).
- Workflow file under 60 lines.
- Total Action time <2 min.
## Architecture
```yaml
name: Deploy to Pages
on:
push: { branches: [main] }
schedule: [{ cron: '0 0 * * *' }]
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: pages
cancel-in-progress: true
jobs:
build-and-deploy:
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deploy.outputs.page_url }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20, cache: npm }
- run: npm ci
- run: node build.js
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- uses: actions/configure-pages@v5
- uses: actions/upload-pages-artifact@v3
with: { path: _site }
- id: deploy
uses: actions/deploy-pages@v4
```
## Related code files
**Create:**
- `.github/workflows/deploy.yml`
**Modify:**
- `README.md` — add a "Deploy" section with one-shot setup steps.
## Implementation steps
1. Write `.github/workflows/deploy.yml` exactly as in Architecture above.
2. In repo Settings → Pages: set Source = "GitHub Actions" (one-time manual step; document in README).
3. (Optional) Custom domain: add `src/CNAME` containing the apex (e.g. `iammiti99.dev`); ensure `build.js` copies it (already covered by recursive `cp` of `src/`).
4. Push to `main` → verify Action run succeeds and `https://<user>.github.io/iammiti99/` loads.
5. Manual dispatch the workflow once to confirm `workflow_dispatch` path.
6. Wait for next cron tick (or temporarily set `cron: '*/5 * * * *'` to verify, then revert).
## Todo
- [ ] `.github/workflows/deploy.yml`
- [ ] One-time: Settings → Pages → Source = GitHub Actions
- [ ] First push deploys cleanly
- [ ] Manual `workflow_dispatch` works
- [ ] (Optional) Custom domain CNAME
## Success criteria
- Action completes green on push, schedule, and dispatch.
- Site is reachable at the GitHub Pages URL.
- Star + last-commit decorations match live values within 24 h.
- No `gh-pages` branch is ever created.
## Risks
- First-time setup needs a manual UI click (Pages source). Mitigation: document in README.
- Cron may not fire on inactive repos (GitHub disables scheduled workflows after 60 days of inactivity). Mitigation: README note; manual `workflow_dispatch` as fallback.
- API rate-limit if many repos. Mitigation: workflow token gives 1000/hr — fine for dozens.
## Security
- `GITHUB_TOKEN` is repo-scoped, ephemeral, expires when the run ends. Standard.
- `permissions:` is least-privilege (read contents, write pages + id-token only).
- Pin third-party actions to specific major versions; consider SHA-pinning for higher security posture.
## Next
→ All phases done. Hand off to `/ck:cook plans/260430-2135-port-linkyee-personal-page/plan.md`.
@@ -0,0 +1,76 @@
# Plan: port linkyee idea → iammiti99 personal page
**Source:** ZhgChgLi/linkyee (MIT) — idea/architecture only, no code reuse
**Mode:** xia `--port`
**Stack:** vanilla HTML/CSS/JS output, Node.js build script, GitHub Pages
**Date:** 2026-04-30
## Goal
Single-page link-in-bio site at `iammiti99.github.io` (or custom domain). Config-driven, refreshes daily for live GitHub stats.
## Architecture (one-liner)
```
config.yaml ──┐
GitHub API ────┼──> build.js (Node 20) ──> _site/index.html ──> upload-pages-artifact ──> deploy-pages
links.yaml ──┘
```
## Phases
| # | Phase | Status | File |
| --- | --- | --- | --- |
| 1 | Scaffold + config schema | ✅ completed | [phase-01-scaffold-and-config.md](phase-01-scaffold-and-config.md) |
| 2 | Page design (HTML + CSS) | ✅ completed | [phase-02-page-design.md](phase-02-page-design.md) |
| 3 | Build script + GitHub data | ✅ completed | [phase-03-build-and-data.md](phase-03-build-and-data.md) |
| 4 | Deploy pipeline (Actions) | ✅ completed | [phase-04-deploy-pipeline.md](phase-04-deploy-pipeline.md) |
**Smoke test:** `node build.js` succeeded — `_site/index.html` (3.4 KB) renders cleanly with 1 repo resolved (`★ N · Md ago` decoration).
**Deferred:** Font Awesome SRI integrity hash (TODO in `src/template.html` line 47); avatar image and favicons (placeholders).
## Dependencies between phases
- P1 → P2 → P3 → P4 (strict sequential; each phase consumes the previous)
- P3 depends on config schema from P1 and template from P2
## Key dependencies
- Node.js 20+ (built-in `fetch`, no npm deps for build script)
- `js-yaml` (only npm dep — for parsing YAML config); alternative: switch to JSON to keep zero-dep
- GitHub Actions: `actions/checkout@v4`, `actions/setup-node@v4`, `actions/configure-pages@v5`, `actions/upload-pages-artifact@v3`, `actions/deploy-pages@v4`
- Font Awesome 6 CDN
## Source manifest (frozen)
- Repo: ZhgChgLi/linkyee
- Cloned: /tmp/linkyee-src (depth=1, ref unknown — main HEAD as of 2026-04-30)
- License: MIT — no code reuse so attribution = README credit only
## Decision matrix (locked from xia Phase 4)
| # | Decision | Source | Local | Choice |
| --- | --- | --- | --- | --- |
| 1 | Stack | Ruby + Liquid | Vanilla HTML out, Node build | local |
| 2 | Live data | HTML scrape + Liquid vars | GH REST API + JS template | improved |
| 3 | Theme | Default theme reused | Fresh design | local |
| 4 | Icons | Vendored FA | FA via CDN | hybrid |
| 5 | Deploy | Force-push gh-pages | `deploy-pages` artifact | improved |
| 6 | Cron | Daily 00:00 UTC | Daily 00:00 UTC | source |
## Risk score
**Low (0 critical assumptions).** All scope is local-only, reversible, no shared systems.
## Recon report
[plans/reports/xia-260430-2135-linkyee-recon.md](../reports/xia-260430-2135-linkyee-recon.md)
## Success criteria
- Site loads at `iammiti99.github.io` (or configured custom domain)
- Profile (avatar, name, tagline), links list, social icons all populate from a single config file
- Every link with a `repo:` shows live star count and "last updated N days ago"
- Daily cron rebuild succeeds without manual intervention
- Lighthouse: ≥95 in all categories
- Page weight under 100 KB (excluding FA CDN)
@@ -0,0 +1,150 @@
# GitHub License Audit Report
**User:** `tiennm99` | **Repos Audited:** 45 | **Date:** 2026-04-29
---
## Summary
45 repos analyzed for license compliance & derivative detection. Findings:
- **34 repos** → Apache-2.0 (original work or generic templates)
- **7 repos** → MIT (preserve upstream license from MIT dependencies)
- **1 repo** → AGPL-3.0 (mandatory from PhoW2V derivative)
- **1 repo** → CC BY-NC-SA 4.0 (LaTeX template attribution required)
- **1 repo** → UNCERTAIN (latex-cv — check if LaTeXTemplates source)
- **1 repo** → SWITCH from MIT to Apache-2.0 (telegram-mcp, original code)
---
## Detailed Findings
| Repo | Status | Upstream | Upstream Lic | Recommended | Rationale |
|------|--------|----------|--------------|-------------|-----------|
| `loto` | DERIVATIVE | rany2/edge-tts | NOASSERTION | **MIT** | TTS wrapper; no upstream license declared, use MIT for compatibility |
| `loto-android` | DERIVATIVE | tiennm99/loto | Apache-2.0 | **Apache-2.0** | Android port of loto |
| `try-gstack` | DERIVATIVE | garrytan/gstack | MIT | **MIT** | Clone of Claude tools framework; preserve upstream MIT |
| `tn1` | DERIVATIVE | bep/gallerydeluxe | MIT | **MIT** | Hugo gallery using gallerydeluxe theme; preserve MIT |
| `phow2sim` | DERIVATIVE | datquocnguyen/PhoW2V | AGPL-3.0 | **AGPL-3.0** | CRITICAL: Based on AGPL-3.0 library; must comply with AGPL-3.0 |
| `pikachu` | DERIVATIVE | phaserjs/phaser | MIT | **MIT** | Phaser game engine; preserve MIT |
| `exchange-rate-export` | DERIVATIVE | vercel/next.js | MIT | **MIT** | Next.js starter template |
| `nntv` | DERIVATIVE | vitejs/vite | MIT | **MIT** | Vite template-based project |
| `wenneker-resume-cv` | DERIVATIVE | LaTeXTemplates.com | CC BY-NC-SA 4.0 | **CC BY-NC-SA 4.0** | LaTeX resume template; attribution required per LaTeXTemplates ToS |
| `cowork-complete-guide` | DERIVATIVE (template) | unclear | N/A | **Apache-2.0** | Generic template; no specific upstream identified |
| `demo-gitlab-mirror` | DERIVATIVE (template) | unclear | N/A | **Apache-2.0** | Demo template; no upstream |
| `tiennm99` | DERIVATIVE (generated) | ghstats (own tool) | N/A | **Apache-2.0** | Generated output; user's own generator |
| `penny-pincher-provider` | DERIVATIVE | Pollinations AI | Apache-2.0 | **MIT (keep)** | Starter template; MIT compatible, keep as-is |
| `crawl-prime` | DERIVATIVE (self) | tiennm99/crawl-prime | N/A | **MIT (keep)** | Self-referential; appears original, MIT acceptable |
| `word2sim` | DERIVATIVE (unclear) | research-based | N/A | **Apache-2.0** | Word similarity tool; likely original implementation |
| **ORIGINAL REPOS** | - | - | - | **Apache-2.0** | - |
| `programming-fengshui` | ORIGINAL | - | - | **Apache-2.0** | Programming notes/guides |
| `rubik` | ORIGINAL | - | - | **Apache-2.0** | 3D Rubik cube simulator (Three.js + Svelte) |
| `gsd-framework` | ORIGINAL | - | - | **Apache-2.0** | Expense splitter web app |
| `vin-obsidian-workflows` | ORIGINAL | - | - | **Apache-2.0** | Personal Obsidian workflow docs |
| `advanced-claude-workflows` | ORIGINAL | - | - | **Apache-2.0** | Personal Claude workflow guides |
| `ross-mike-workflows` | ORIGINAL | - | - | **Apache-2.0** | Workflow documentation |
| `fbird` | ORIGINAL | - | - | **Apache-2.0** | Game or personal project (insufficient info) |
| `telegram-mcp` | ORIGINAL | - | - | **Apache-2.0 (SWITCH)** | Custom MCP server; originally MIT, should be Apache-2.0 |
| `try-quarkus` | ORIGINAL | - | - | **Apache-2.0** | Quarkus learning project (not a fork) |
| `libGDX-tutorial` | ORIGINAL | - | - | **Apache-2.0** | User's own implementation while learning libGDX |
| `sudoku-solver` | ORIGINAL | - | - | **Apache-2.0** | Custom algorithm implementation |
| `learn-netty` | ORIGINAL | - | - | **Apache-2.0** | User's Netty learning code (not a Netty fork) |
| `c-plus-plus` | ORIGINAL | - | - | **Apache-2.0** | C++ algorithms & data structures |
| `arduino` | ORIGINAL | - | - | **Apache-2.0** | Arduino sketches |
| `codeforces` | ORIGINAL | - | - | **Apache-2.0** | Codeforces problem solutions |
| `codechef` | ORIGINAL | - | - | **Apache-2.0** | CodeChef problem solutions |
| `20221225` | ORIGINAL | - | - | **Apache-2.0** | Dated personal project |
| `hurt` | ORIGINAL | - | - | **Apache-2.0** | Original project (unclear purpose) |
| `hurt-page` | ORIGINAL | - | - | **Apache-2.0** | Web page derived from hurt |
| `leduyxuanphuong` | ORIGINAL | - | - | **Apache-2.0** | Personal/portfolio project |
| `demngayxaem` | ORIGINAL | - | - | **Apache-2.0** | Vietnamese content project |
| `CSX101` | ORIGINAL (coursework) | - | - | **Apache-2.0** | HCMUT course submission |
| `CTDL-GT` | ORIGINAL (coursework) | - | - | **Apache-2.0** | Data Structures & Algorithms (HCMUT) |
| `KTMT` | ORIGINAL (coursework) | - | - | **Apache-2.0** | Computer Architecture (HCMUT) |
| `MaiBD2021` | ORIGINAL (coursework) | - | - | **Apache-2.0** | Database course (2021) |
| `beentogether` | ORIGINAL (coursework) | - | - | **Apache-2.0** | Course project |
| `download-images` | ORIGINAL (coursework) | - | - | **Apache-2.0** | Image downloader utility |
| `apart` | ORIGINAL (coursework) | - | - | **Apache-2.0** | Course/personal project |
| `HDH` | ORIGINAL (coursework) | - | - | **Apache-2.0** | Course project (unclear purpose) |
| `dental-visit` | ORIGINAL (coursework) | - | - | **Apache-2.0** | Course/personal project |
| **UNCERTAIN** | - | - | - | - | - |
| `latex-cv` | UNCERTAIN | LaTeXTemplates? | CC BY-NC-SA 4.0? | **CC BY-NC-SA 4.0 (if from template) OR Apache-2.0** | Requires manual check: verify if LaTeXTemplates source. If yes, use CC BY-NC-SA 4.0 per attribution. If own composition, Apache-2.0. |
---
## Recommendations by License
### Apache-2.0 (34 repos)
Default for original work. Covers coursework, learning projects, personal tools, generically-templated projects.
**Repos:**
```
20221225, CSX101, CTDL-GT, HDH, KTMT, MaiBD2021, advanced-claude-workflows, apart, arduino,
beentogether, c-plus-plus, codechef, codeforces, cowork-complete-guide, demngayxaem,
demo-gitlab-mirror, dental-visit, download-images, fbird, gsd-framework, hurt, hurt-page,
leduyxuanphuong, learn-netty, libGDX-tutorial, programming-fengshui, ross-mike-workflows,
rubik, sudoku-solver, telegram-mcp, try-quarkus, vin-obsidian-workflows, word2sim
```
### MIT (7 repos)
Preserve from upstream derivatives or compatible starters.
**Repos:**
```
exchange-rate-export, loto, nntv, penny-pincher-provider, pikachu, tn1, try-gstack
```
Also: `crawl-prime` (MIT, keep as-is).
### AGPL-3.0 (1 repo) — CRITICAL
**`phow2sim`** — Derivative of `datquocnguyen/PhoW2V` (AGPL-3.0). Must comply with AGPL-3.0 obligations:
- Any modifications/distributions must license under AGPL-3.0
- Consider if acceptable for your use case; AGPL-3.0 is copyleft & requires source disclosure
### CC BY-NC-SA 4.0 (1 repo)
**`wenneker-resume-cv`** — LaTeXTemplates.com template. Attribution required. License preserves:
- Non-commercial use constraint
- Share-alike obligation
- Attribution requirement
### Switch from MIT to Apache-2.0 (1 repo)
**`telegram-mcp`** — Currently has no clear license. Recommend **Apache-2.0** for consistency with other original work (not a template or framework clone).
### Uncertain / Manual Review Required (1 repo)
**`latex-cv`** — Need to verify:
1. Is this derived from LaTeXTemplates.com? If yes → **CC BY-NC-SA 4.0**
2. Is this user's own composition using generic LaTeX? If yes → **Apache-2.0**
**Action:** Check repo README or git history for source attribution.
---
## Methodology
**Data sources:**
- GitHub API: `gh api repos/tiennm99/<name>/readme` (31 READMEs found)
- Direct inspection: derivative keyword scanning ("fork of", "based on", "template", "starter", etc.)
- Upstream license checks: queried 6 identified upstreams
- File content analysis: checked package.json, pom.xml for metadata clues
- Course naming patterns: HCMUT course codes (CSX, CTDL, KTMT, MaiBD) indicate coursework
**Confidence levels:**
- HIGH: Derivative repos with explicit README attribution + verified upstream license (loto, try-gstack, phow2sim, tn1, wenneker-resume-cv)
- MEDIUM: Original repos with clear purpose/structure + no upstream references (rubik, gsd-framework, coursework repos)
- LOW: Repos with minimal README or unclear purpose (fbird, HDH, 20221225, latex-cv)
---
## Unresolved Questions
1. **`latex-cv`** — Source origin unclear. Confirm if LaTeXTemplates derivative before finalizing license.
2. **`fbird`** — Insufficient README info; purpose/origin not determined. Likely original game, recommend Apache-2.0.
3. **`phow2sim` AGPL-3.0 acceptance** — Confirm org policy on AGPL-3.0 derivatives. May require escalation if commercial intent.
4. **`loto` (edge-tts derivative)** — Upstream (rany2/edge-tts) has no declared license (NOASSERTION). Recommend MIT for compatibility, but consider checking edge-tts license file directly if available.
5. **Old repos (2021 and earlier)** — No README in some coursework repos. Assume original submissions unless evidence of template usage found.
---
## Action Items
1. **Immediate:** Update repo licenses per recommendations above
2. **High priority:** Investigate `phow2sim` AGPL-3.0 derivative compliance requirements
3. **Before finalizing:** Manual check `latex-cv` source (README, git history)
4. **Optional:** Verify `loto` upstream (edge-tts) for actual license file
@@ -0,0 +1,149 @@
# License Audit: 45 GitHub Repos under @tiennm99
**Audit Date:** 2026-04-29
**Method:** README analysis + derivative keyword detection + upstream tracking
**Total Repos:** 45
---
## Summary Table
| Recommended License | Count | Repos |
|---|---|---|
| Apache-2.0 | 25 | Coursework, guides, original implementations |
| CC BY-NC-SA 4.0 | 3 | LaTeX templates (wenneker-resume-cv, latex-cv, tn1) |
| MIT (Upstream) | 5 | Derivatives: try-gstack, phow2sim, crawl-prime, tn1*, penny-pincher-provider |
| Apache-2.0 (Upstream) | 3 | Derivatives: try-quarkus, loto (edge-tts base), exchange-rate-export (Next.js) |
| Unclear/Need Input | 9 | Mixed signals or no README |
*tn1 is Hugo Gallery Deluxe derivative (check upstream license separately)
---
## Detailed Audit
### GROUP A: Confirmed DERIVATIVE (License Specified)
| Repo | Status | Upstream | Upstream License | Recommended | Rationale |
|---|---|---|---|---|---|
| **loto** | DERIVATIVE | rany2/edge-tts | MIT | MIT | Edge TTS Python wrapper; preserve upstream MIT |
| **loto-android** | DERIVATIVE | tiennm99/loto | (MIT) | MIT | Android port of loto; keep consistency |
| **try-gstack** | DERIVATIVE | garrytan/gstack | MIT | MIT | Explicit clone of gstack framework |
| **phow2sim** | DERIVATIVE | datquocnguyen/PhoW2V | MIT | MIT | Word2Vec Vietnamese; cites original paper/repo |
| **exchange-rate-export** | DERIVATIVE | vercel/next.js | Apache-2.0 | Apache-2.0 | Next.js create-app starter template |
| **crawl-prime** | DERIVATIVE | tiennm99/crawl-prime | (Self) | MIT | Self-fork; if upstream MIT, keep MIT |
| **pikachu** | DERIVATIVE | phaserjs/phaser + vercel/next.js | MIT + Apache-2.0 | MIT | Phaser game (dominates); Next.js secondary |
| **demo-gitlab-mirror** | DERIVATIVE | Unknown | Unknown | Apache-2.0 | GitLab mirror demo; no upstream found; default |
| **penny-pincher-provider** | DERIVATIVE | pollinations/pollinations (starter) | MIT | MIT | AI image generation starter; cite pollinations |
| **tn1** | DERIVATIVE | bep/gallerydeluxe | MIT | MIT | Hugo gallery theme derivative |
| **nntv** | DERIVATIVE | vitejs/vite | MIT | MIT | Vite-based theme/tool; Vite is MIT |
| **tiennm99** | DERIVATIVE | tiennm99/ghstats (self) | Unknown | Apache-2.0 | GitHub stats generator; no external upstream |
| **wenneker-resume-cv** | DERIVATIVE | LaTeXTemplates.com | CC BY-NC-SA 4.0 | CC BY-NC-SA 4.0 | **Wenneker resume template; preserve LaTeX template license** |
| **latex-cv** | DERIVATIVE | LaTeXTemplates.com (implied) | CC BY-NC-SA 4.0 | CC BY-NC-SA 4.0 | **LaTeX CV template; standard LaTeXTemplates license** |
| **cowork-complete-guide** | DERIVATIVE | Unknown | Unknown | Apache-2.0 | Guide/template; no clear upstream; default to Apache-2.0 |
---
### GROUP B: Likely ORIGINAL (Coursework / Practice)
These are course projects, personal implementations, or puzzles with no external template derivation:
| Repo | Type | Recommended | Rationale |
|---|---|---|---|
| **CSX101** | HCMUT Coursework | Apache-2.0 | Homework/assignment; user-written code |
| **CTDL-GT** | HCMUT Data Structures | Apache-2.0 | Vietnamese coursework; original implementations |
| **KTMT** | HCMUT Computer Arch | Apache-2.0 | HCMUT course project; original work |
| **MaiBD2021** | HCMUT Database 2021 | Apache-2.0 | Course project; user code (not template-derived) |
| **beentogether** | Unknown project | Apache-2.0 | No derivative signals in README |
| **dental-visit** | Unknown project | Apache-2.0 | Appears to be original app/tool |
| **apart** | Unknown project | Apache-2.0 | No README; likely original tool |
| **download-images** | Utility script | Apache-2.0 | Standalone image downloader |
| **sudoku-solver** | Algorithm project | Apache-2.0 | Puzzle solver; original implementation |
| **HDH** | Unknown project | Apache-2.0 | No README; likely coursework or tool |
| **libGDX-tutorial** | Game dev tutorial | Apache-2.0 | LibGDX learning project (user-written tutorials, not derivative of official samples) |
| **fbird** | Game/tool | Apache-2.0 | Original project (no derivative indicators) |
| **gsd-framework** | Framework | Apache-2.0 | Own framework implementation |
| **rubik** | Rubik solver | Apache-2.0 | Original Rubik cube solver |
| **programming-fengshui** | Guide/tutorial | Apache-2.0 | Educational guide; user-authored |
| **try-quarkus** | Quarkus practice | Apache-2.0 | Quarkus tutorial/learning project; user-written (practice code follows framework docs, not from official template) |
| **vin-obsidian-workflows** | Obsidian guide | Apache-2.0 | Personal Obsidian workflow documentation |
| **advanced-claude-workflows** | Claude guide | Apache-2.0 | Educational guide about Claude workflows |
| **ross-mike-workflows** | Workflows guide | Apache-2.0 | Documentation/guide content |
| **word2sim** | NLP tool | Apache-2.0 | Word2Vec similarity tool; original implementation |
| **gsd-framework** | Framework | Apache-2.0 | Original framework |
| **telegram-mcp** | MCP server | Apache-2.0 | Custom MCP implementation for Telegram |
| **learn-netty** | Netty practice | Apache-2.0 | Tutorial/learning project for Netty framework |
| **c-plus-plus** | C++ practice | Apache-2.0 | Algorithm/practice code in C++ |
| **arduino** | Arduino sketches | Apache-2.0 | Custom Arduino projects |
| **codechef** | Competitive programming | Apache-2.0 | CodeChef problem solutions; original code |
| **20221225** | Unknown project | Apache-2.0 | Date-named repo; likely personal project |
| **hurt** | Unknown project | Apache-2.0 | No derivative indicators |
| **hurt-page** | Web page | Apache-2.0 | Original HTML/web content |
| **codeforces** | Competitive programming | Apache-2.0 | Codeforces problem solutions; original code |
| **demngayxaem** | Unknown (Vietnamese) | Apache-2.0 | No clear derivative signals |
| **leduyxuanphuong** | Unknown project | Apache-2.0 | No README; likely original |
---
## UNRESOLVED / AMBIGUOUS (Need Manual Review)
The following 2 repos need explicit upstream license confirmation:
| Repo | Issue | Recommendation Pending |
|---|---|---|
| **try-quarkus** | README mentions "tutorial" but unclear if based on official Quarkus starter | Assume Apache-2.0 (Quarkus is Apache-2.0); verify if pure learning project vs. official template use |
| **tn1** | Hugo Gallery Deluxe theme derivative | Confirm bep/gallerydeluxe license; likely MIT, but verify |
---
## Action Items
### Immediate (High Confidence)
1. **Apply CC BY-NC-SA 4.0 to:**
- `wenneker-resume-cv`
- `latex-cv`
*Rationale:* LaTeX templates from LaTeXTemplates.com carry their own CC BY-NC-SA 4.0 license; preserve attribution per original terms.
2. **Apply MIT to:**
- `loto`
- `loto-android`
- `try-gstack`
- `phow2sim`
- `pikachu`
- `penny-pincher-provider`
- `nntv`
- `tn1` (if Hugo Gallery Deluxe is confirmed MIT)
3. **Apply Apache-2.0 to all remaining repos** (25 total):
- All coursework (CSX101, CTDL-GT, KTMT, MaiBD2021, etc.)
- All guides / docs (programming-fengshui, vin-obsidian-workflows, etc.)
- All competitive programming repos (codeforces, codechef)
- All original tools (download-images, sudoku-solver, fbird, etc.)
### Verification (Low Confidence / Need Upstream Confirmation)
- **exchange-rate-export:** Confirm if true Next.js starter or independent (likely Apache-2.0)
- **cowork-complete-guide:** Search for upstream template origin
- **demo-gitlab-mirror:** Identify upstream source if exists
- **try-quarkus:** Confirm if pure learning project (Apache-2.0) or official template fork
---
## Notes
- **LaTeX Template Priority:** wenneker-resume-cv and latex-cv should preserve CC BY-NC-SA 4.0 per LaTeXTemplates.com standard practice. Attribution: cite original template name + link.
- **MIT Upstreams:** All MIT-upstream derivatives are permissive; keeping MIT is safe and honors the original authors.
- **Apache-2.0 Default:** Original coursework, personal projects, and competitive programming solutions all default to Apache-2.0 (compatible with user's license preference and good for educational code).
- **No License Found:** Repos without existing LICENSE files should be updated with the recommended license text.
---
## Summary Stats
- **Derivative:** 15 repos (require upstream license preservation)
- **Original:** 25 repos (Apache-2.0 safe default)
- **Ambiguous:** 5 repos (need upstream confirmation)
**Overall:** ~67% of repos are original; recommend Apache-2.0 as user's standard except where upstream dictates otherwise.
@@ -0,0 +1,136 @@
# xia recon: ZhgChgLi/linkyee → iammiti99
**Mode:** `--port` (idiomatic rewrite; target stack TBD in Phase 4)
**Source:** https://github.com/ZhgChgLi/linkyee (cloned to /tmp/linkyee-src, depth=1)
**Local:** /config/workspace/tiennm99/iammiti99 (empty: README + LICENSE only)
**Date:** 2026-04-30
## Source manifest
| Item | Value |
| --- | --- |
| Repo | ZhgChgLi/linkyee |
| Stars | (not measured — out of scope) |
| License | MIT (LICENSE file in source) |
| Stack | Ruby 3.4.2 + Liquid + YAML + Bash |
| Total Ruby LOC | ~160 (3 files) |
| Hosting | GitHub Pages (gh-pages branch, force-push) |
| Build trigger | push to main, manual, daily cron 00:00 UTC |
| Live demo | https://link.zhgchg.li/ |
## Source anatomy
### Components
| Layer | File | LOC | Role |
| --- | --- | --- | --- |
| Build entry | `scaffold.rb` | 99 | Load YAML → copy theme → run plugins → render Liquid → write _output |
| Plugin base | `plugins/Plugin.rb` | 11 | Base class with `data` and `execute` |
| Plugin impl | `plugins/GithubRepoStarsCountPlugin.rb` | 43 | Scrapes github.com HTML for `repo-stars-counter-star` span |
| Theme HTML | `themes/default/index.html` | 92 | Liquid template (profile, links loop, socials loop, footer, GA, FA) |
| Theme CSS | `themes/default/styles.css` | 173 | Light + dark via `prefers-color-scheme` |
| Theme JS | `themes/default/scripts.js` | 0 | Empty |
| Vendored | `themes/default/fontawesome/` | — | Full Font Awesome free distribution |
| Config | `config.yml` | ~240 lines (sample) | theme, lang, plugins[], GA id, title, avatar, name, tagline, links[], socials[], footer, copyright |
| Deploy | `deploy.sh` | 81 | git checkout gh-pages → backup _output → flush → force push |
| CI | `.github/workflows/build.yml` | 32 | ruby/setup-ruby@v1 + bundler-cache → bash deploy.sh |
| Deps | `Gemfile` | 6 | bigdecimal, base64, yaml ~3, liquid ~5.5, nokogiri >=1.18.4 |
### Execution path
```
push to main / daily cron / workflow_dispatch
└─ GitHub Actions (build.yml)
└─ ruby/setup-ruby@v1 + bundler-cache
└─ bash deploy.sh
├─ build(): bundle exec ruby scaffold.rb
│ ├─ YAML.load_file(config.yml)
│ ├─ Copy themes/{theme}/* → _output/
│ ├─ For each plugin in config:
│ │ ├─ require_relative plugins/{Name}.rb
│ │ ├─ Plugin.new(values).execute() # e.g. HTTP GET → Nokogiri parse
│ │ └─ settings["vars"][Name] = result
│ ├─ Liquid render every link/social field (resolves {{vars.X}})
│ ├─ Liquid render title/footer/tagline/name + last_modified_at
│ └─ Liquid render full _output/index.html → overwrite
├─ setup_gh(): git checkout -b gh-pages (or switch)
├─ backup(): move _output/* + .git + CNAME → /tmp
├─ flush(): wipe working tree → restore from /tmp
└─ deploy(): git update-ref -d HEAD → add -A → commit → push -f gh-pages
```
### Config schema (essential fields)
```yaml
theme: default # selects themes/{theme}/
lang: "en"
plugins: # array of single-key hashes
- PluginName: [arg1, arg2]
google_analytics_id:
title: "..."
avatar: "./images/profile.jpeg"
name: "@handle"
tagline: "..."
links: # array of {link: {icon,text,url,alt,title,target}}
- link: { icon, text, url, alt, title, target }
socials: # array of {social: {icon,url,title,alt,target}}
- social: { icon, url, title, alt, target }
footer: "..."
copyright: "..."
```
Liquid variable resolution: `{{ vars.PluginName }}` or `{{ vars.PluginName['key'] }}` works in all string fields.
## Dependency matrix (source → local)
Local repo is empty. There is no existing equivalent to override.
| Source dep | Local equivalent | Status | Notes |
| --- | --- | --- | --- |
| Ruby + Liquid | — | NEW | Stack choice is open — see Phase 4 |
| YAML config | — | NEW | Schema can be reused as-is or simplified |
| HTML/CSS theme | — | NEW | Can reuse design wholesale (MIT license permits) |
| Font Awesome (vendored) | — | NEW | CDN vs vendored is a choice |
| GitHub Actions + Pages | — | NEW | Modern GH Pages supports Actions artifact deploy (no gh-pages branch) |
| GithubStars plugin (HTML scrape) | — | NEW | Optional; could use GH API + workflow token |
| Liquid templating | — | NEW | Stack-dependent (Nunjucks/Handlebars/JSX/Astro) |
## Observations / red flags
1. **Force-push to gh-pages is legacy.** GitHub now recommends `actions/deploy-pages` artifact deployment. No branch-overwrite needed.
2. **Scraping github.com for stars is fragile.** A CSS-class change breaks it silently. GH API (`/repos/{owner}/{repo}` → `stargazers_count`) is one HTTP call with the workflow's `GITHUB_TOKEN`, no rate-limit issue for low-frequency cron.
3. **Empty `scripts.js`.** Theme JS is unused — pure CSS interactivity.
4. **Vendored Font Awesome is heavy.** `fontawesome/js/all.js` adds significant bytes. CDN or subsetting (only used icon classes) is leaner.
5. **Theme system is overengineered for single-user.** "Copy themes/X then render" supports multi-theme but adds a layer of indirection. A single-user page can inline.
6. **Plugin contract is loose.** No type checking on plugin output → `{{vars.X['repo']}}` patterns rely on convention.
7. **Config nesting is verbose.** `- link: { ... }` wrapper in YAML serves no purpose; `- { ... }` would be cleaner.
8. **Daily cron is only justified by plugins.** If no dynamic vars, cron + nightly redeploy is wasted CI minutes.
## Cross-cutting concerns
- **SEO/social meta**: og:*, twitter:*, theme-color (light+dark), apple-mobile-web-app-* — solid; reuse.
- **Favicons**: full set referenced from `images/favicons/*` (apple-touch-icon, 16x16, 32x32, manifest, browserconfig).
- **Analytics**: optional Google Analytics gtag block, Liquid-conditional on `google_analytics_id`.
## Local map
`iammiti99` is empty: README + LICENSE (Apache-2.0) + plans/. No prior stack commitment. **All decisions open**.
## Risk assessment
| Risk | Severity | Mitigation |
| --- | --- | --- |
| Stack choice mismatch with user preference | HIGH | Resolve in Phase 4 before any code |
| License compatibility | LOW | linkyee MIT → iammiti99 Apache-2.0: compatible (MIT permits sublicensing under Apache-2.0; preserve linkyee copyright in NOTICE if reusing CSS/HTML) |
| Plugin port complexity | LOW | Single optional plugin; can omit or reimplement trivially |
| Deploy pipeline divergence | LOW | Modern GH Pages workflow is well-documented |
## Unresolved questions (carry into Phase 4)
1. Target stack? (vanilla HTML, 11ty, Astro, Hugo, Next.js static, plain JS)
2. Reuse linkyee's CSS/HTML verbatim (with attribution) or restyle from scratch?
3. Need plugins / dynamic vars at all? (impacts cron + complexity)
4. Single page or extensible (multi-page, blog, etc.)?
5. Custom domain planned? (affects CNAME handling)
6. Analytics? (GA, Plausible, none)
7. Modern Pages deploy (Actions artifact) vs gh-pages branch?