@tidyfactor/doc 1.3.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.
Files changed (43) hide show
  1. package/.tidyfactor +16 -0
  2. package/AGENTS.md +10 -0
  3. package/CHANGELOG.md +75 -0
  4. package/LICENSE +17 -0
  5. package/README.ar.md +180 -0
  6. package/README.de.md +44 -0
  7. package/README.es.md +44 -0
  8. package/README.fa.md +44 -0
  9. package/README.fr.md +44 -0
  10. package/README.md +198 -0
  11. package/README.pt.md +44 -0
  12. package/README.zh.md +44 -0
  13. package/SKILL.md +36 -0
  14. package/assets/hero-banner.png +0 -0
  15. package/assets/og-default.png +0 -0
  16. package/bin/add-skill.js +27 -0
  17. package/brand.json +13 -0
  18. package/package.json +59 -0
  19. package/references/commands/collect.md +15 -0
  20. package/references/commands/docsify.md +15 -0
  21. package/references/commands/generate.md +23 -0
  22. package/references/commands/init.md +15 -0
  23. package/references/commands/mkdocs.md +15 -0
  24. package/references/commands/site.md +23 -0
  25. package/references/memory/collection-sources.md +47 -0
  26. package/references/memory/doc-templates.md +73 -0
  27. package/references/memory/doc-tree.md +37 -0
  28. package/references/memory/docsify-config.md +273 -0
  29. package/references/memory/mkdocs-config.md +170 -0
  30. package/references/memory/site-engines.md +54 -0
  31. package/references/memory/stacks/js-ts.md +45 -0
  32. package/references/memory/stacks/php.md +33 -0
  33. package/references/memory/stacks/react-vue-next.md +50 -0
  34. package/references/workflows/collect.md +25 -0
  35. package/references/workflows/docsify.md +19 -0
  36. package/references/workflows/generate-api.md +21 -0
  37. package/references/workflows/generate-guide.md +20 -0
  38. package/references/workflows/generate-inline.md +20 -0
  39. package/references/workflows/generate-readme.md +20 -0
  40. package/references/workflows/init-docs.md +18 -0
  41. package/references/workflows/mkdocs.md +44 -0
  42. package/tools/build-skill.js +152 -0
  43. package/tools/validate_skill.py +124 -0
@@ -0,0 +1,50 @@
1
+ # Memory: stacks/react-vue-next
2
+
3
+ Component-level documentation conventions, layered on top of `js-ts.md` (still use JSDoc/TSDoc block syntax — this file adds what's specific to components, pages, and routes).
4
+
5
+ ## React — props, not just function signature
6
+
7
+ ```tsx
8
+ /**
9
+ * <one-line summary of what the component renders/does>
10
+ */
11
+ interface ButtonProps {
12
+ /** Description of this prop. */
13
+ label: string;
14
+ /** Optional, defaults to 'primary'. */
15
+ variant?: 'primary' | 'secondary';
16
+ /** Called when clicked. */
17
+ onClick?: () => void;
18
+ }
19
+ ```
20
+
21
+ - Document props via the `interface`/`type` block (per-member comments), not a `@param` list on the component function — that's the idiomatic React pattern and what most tooling (Storybook, TypeDoc) expects.
22
+ - Note default values from the actual destructured defaults or `defaultProps`, not assumed.
23
+
24
+ ## Vue — SFC `<script>` block comments + `defineProps`
25
+
26
+ ```vue
27
+ <script setup lang="ts">
28
+ /**
29
+ * <one-line summary>
30
+ */
31
+ defineProps<{
32
+ /** Description. */
33
+ label: string;
34
+ /** Optional, defaults to 'primary'. */
35
+ variant?: 'primary' | 'secondary';
36
+ }>();
37
+ </script>
38
+ ```
39
+
40
+ - For Options API components (no `<script setup>`), document each prop inside the `props: {}` object with a comment above it instead.
41
+ - Emitted events (`defineEmits` / `this.$emit`) get documented the same way props do — name, payload type, when it fires (from code parsing + error-patterns findings if it's an error event).
42
+
43
+ ## Next.js — pages/routes get a route-level note, not just a component doc
44
+
45
+ - For a page/route file, prepend a comment noting the route path, whether it's a Server or Client Component, and any `params`/`searchParams` it reads — this is route contract, not just component contract.
46
+ - API routes (`route.ts`/`route.js` under `app/api/`, or `pages/api/`) are documented as API reference (`generate-api`, using `js-ts.md`'s function conventions for the handler), not as component docs.
47
+
48
+ ## What NOT to document
49
+
50
+ - Purely presentational/internal sub-components not exported from the module's public entry point: skip the full prop-table treatment unless they're complex enough that the error-patterns or persona-tracing findings flagged them as maintainer-relevant.
@@ -0,0 +1,25 @@
1
+ # Workflow: collect
2
+
3
+ One outcome: a structured findings file — `docs/.collected/<target>.md` — that `generate` can turn into any doc type without re-deriving facts from the codebase itself. `<target>` is the module, package, API surface, or component named by the request (or the whole project if none was named).
4
+
5
+ ## Steps
6
+
7
+ Run all five collection dimensions from `memory/collection-sources.md` against the target. Skip a dimension only if it genuinely doesn't apply (e.g., no Git history available for an uploaded snapshot) — note the skip and why, don't silently omit it.
8
+
9
+ 1. **Code parsing.** Extract existing docblocks/comments, function/method/class signatures, exported types, and public surface area directly from source. Flag anything already documented inline so `generate` doesn't duplicate it.
10
+ 2. **Commit history.** Read `git log` and any available PR descriptions for the target's files. Pull out *why* behind non-obvious code — rationale, past bugs fixed, deliberate tradeoffs — not just *what* changed.
11
+ 3. **Runtime & environment.** Enumerate required environment variables, config files, software dependencies (with version constraints), and any stated hardware/resource limits. **MANDATORY**: Scrub and redact any actual secrets, production server IPs, database passwords, or private API tokens found in `.env` or config files—record only variable names, expected formats, and generic placeholder values.
12
+ 4. **User persona tracing.** Identify who actually reads docs for this target — API consumers, internal maintainers, end-users — and note which facts matter to which persona (an internal maintainer needs the "why"; an API consumer needs the contract).
13
+ 5. **Error patterns.** Collect how the code fails: thrown exceptions, error codes, logged failure messages, and how each is meant to be handled or surfaced. Scrub any sensitive runtime credentials or local workstation paths that appear inside logged messages.
14
+
15
+ 6. **Write the findings** to `docs/.collected/<target>.md` as plain structured notes under five headings matching the dimensions above — this is source material for `generate`, not a finished doc, so skip prose polish.
16
+ 7. **Update `docs/.doc-manifest.json`**: add `<target>` to the `collected` section with a timestamp.
17
+
18
+ ## Validation checklist
19
+
20
+ - [ ] `docs/.collected/<target>.md` exists and has content (or an explicit "not applicable" note) under all five dimension headings
21
+ - [ ] Every fact traces to something actually found in the code, history, config, or logs — nothing inferred or assumed
22
+ - [ ] Zero sensitive data leaked: all real API keys, passwords, private IPs, and secrets are replaced with safe generic placeholders
23
+ - [ ] No local workstation drive paths (`C:\...`, `file:///...`) exist in findings; all paths are normalized to project-relative paths
24
+ - [ ] `docs/.doc-manifest.json`'s `collected` section includes `<target>`
25
+ - [ ] Findings are organized by dimension, not pre-formatted as any particular doc type
@@ -0,0 +1,19 @@
1
+ # Workflow: docsify
2
+
3
+ One outcome: `/docs` is browsable as a Docsify site — an `index.html` entry point and an auto-generated `_sidebar.md`, wired per `memory/docsify-config.md`.
4
+
5
+ ## Steps
6
+
7
+ 1. **Write `docs/index.html`** using the template in `memory/docsify-config.md` — project name, theme, and the plugin list from that file. Do not hand-roll a different Docsify setup.
8
+ 2. **Generate `docs/_sidebar.md`** by walking the current `/docs` tree per `doc-tree.md`'s structure and listing every existing generated file (skip `.doc-manifest.json` and the `.collected/` folder — those aren't site content). Group by section (API, Guides, root docs) matching the folder structure.
9
+ 3. **Optionally write `docs/_coverpage.md`** if the user wants a landing/cover page — only if asked, per `docsify-config.md`'s note that the coverpage is opt-in, not default.
10
+ 4. **Report how to preview it** (local static server command from `docsify-config.md`) and note the deploy targets listed there (GitHub Pages / Netlify / Cloudflare Pages / cPanel static hosting) without picking one — that's the user's call.
11
+
12
+ ## Validation checklist
13
+
14
+ - [ ] `docs/index.html` exists and matches the template/plugin list in `memory/docsify-config.md`
15
+ - [ ] `docs/_sidebar.md` lists every current doc under `/docs`, correctly grouped, and excludes `.doc-manifest.json`/`.collected/`
16
+ - [ ] All sidebar links use root-relative leading slashes (`/guides/...`, `/api/...`) and never point outside `/docs` (no `../` or `file:///` links)
17
+ - [ ] No real secrets or sensitive server configurations are hardcoded into `docs/index.html` or navigation files
18
+ - [ ] No new documentation *content* was authored by this workflow — only navigation/entry-point files
19
+ - [ ] Preview instructions and deploy target options were reported, with no deploy target chosen unilaterally
@@ -0,0 +1,21 @@
1
+ # Workflow: generate-api
2
+
3
+ One outcome: an API reference file under `docs/api/` for one target, built from `docs/.collected/<target>.md` and formatted per the matching `memory/stacks/*.md` file.
4
+
5
+ ## Steps
6
+
7
+ 1. **Load the findings** from `docs/.collected/<target>.md`. If it doesn't exist, stop — see constraint 2 in `SKILL.md`.
8
+ 2. **Pull the doc-type shape** from `memory/doc-templates.md` (API reference section) and the **comment/tag conventions** from the matching stack file — PHPDoc tags for PHP, JSDoc for JS, TSDoc for TS, or component prop-table conventions for React/Vue/Next.
9
+ 3. **Write `docs/api/<target>.md`**: for every public function/method/endpoint/component found in the findings, document signature, parameters (with types), return value, thrown errors (from the "error patterns" findings), and a short usage example. Prioritize facts an **API consumer** needs (per the persona-tracing findings) over internal rationale. Ensure code examples use dummy/placeholder tokens and endpoints, never real secrets.
10
+ 4. **Cross-link**: if the target has related targets already documented, add clean relative "See also" links between them (e.g. `[Other API](./other.md)`). Never use `file:///` or local drive paths.
11
+ 5. **Update `docs/.doc-manifest.json`**'s `generated` section with the new file and timestamp.
12
+
13
+ ## Validation checklist
14
+
15
+ - [ ] `docs/api/<target>.md` exists and follows the API-reference shape in `memory/doc-templates.md`
16
+ - [ ] Every documented signature matches what `collect` actually found — no invented parameters or return types
17
+ - [ ] Comment/tag style matches the target's stack file exactly (no PHPDoc tags in a TS doc, etc.)
18
+ - [ ] Zero sensitive data leaked (all auth tokens, secrets, private IPs replaced with generic placeholders)
19
+ - [ ] All cross-references use clean relative markdown paths or web URLs; zero `file:///` or local absolute drive paths
20
+ - [ ] Thrown/returned error conditions from the findings are represented
21
+ - [ ] `docs/.doc-manifest.json` updated
@@ -0,0 +1,20 @@
1
+ # Workflow: generate-guide
2
+
3
+ One outcome: a single technical guide file under `docs/guides/`, built from `docs/.collected/<target>.md` and the guide shape in `memory/doc-templates.md`. One guide = one purpose (setup, architecture, or a specific workflow) — a request covering two purposes ("how to set up AND how deploys work") is two guides, run this workflow twice.
4
+
5
+ ## Steps
6
+
7
+ 1. **Identify the guide's single purpose** — setup/getting-started, architecture overview, or a specific operational workflow (e.g., "how releases work"). If the request bundles more than one, split it before continuing.
8
+ 2. **Load the findings** from `docs/.collected/<target>.md`. If it doesn't exist, stop — see constraint 2 in `SKILL.md`.
9
+ 3. **Pull the guide shape** from `memory/doc-templates.md` (guide section) for the identified purpose.
10
+ 4. **Write `docs/guides/<purpose-slug>.md`**, prioritizing facts for the persona that reads this kind of guide (per persona-tracing findings — usually an internal maintainer or a new contributor, not an external API consumer). Ensure all configuration code blocks, commands, IPs, and tokens use safe placeholders. Use clean relative links for cross-references.
11
+ 5. **Update `docs/.doc-manifest.json`**'s `generated` section.
12
+
13
+ ## Validation checklist
14
+
15
+ - [ ] The guide covers exactly one purpose — no bundled setup+architecture+workflow content in one file
16
+ - [ ] `docs/guides/<purpose-slug>.md` exists and follows the shape in `memory/doc-templates.md`
17
+ - [ ] Every step/claim traces to the findings — nothing assumed
18
+ - [ ] Zero sensitive data leaked: all server IPs, DB credentials, API keys, or private paths replaced with placeholders
19
+ - [ ] All internal links are clean relative markdown links; no `file:///` or local drive paths
20
+ - [ ] `docs/.doc-manifest.json` updated
@@ -0,0 +1,20 @@
1
+ # Workflow: generate-inline
2
+
3
+ One outcome: source files in the target with inline documentation comments added or brought up to date, per the matching stack file's conventions. This workflow edits source directly — it does not write anything under `/docs`.
4
+
5
+ ## Steps
6
+
7
+ 1. **Load the findings** from `docs/.collected/<target>.md`, specifically the code-parsing section (what's already documented) and the error-patterns section (what exceptions/failure modes need noting).
8
+ 2. **Load the comment convention** from the matching stack file — PHPDoc block format for PHP, JSDoc for JS, TSDoc for TS, component-comment conventions for React/Vue/Next.
9
+ 3. **For each undocumented (or stale-documented) public function/method/class/component in the target**, add or correct a doc comment: purpose, parameters, return value, thrown/handled errors. Leave already-correct existing comments untouched — this is additive/corrective, not a rewrite of the file.
10
+ 4. **Do not touch private/internal implementation details** that don't need a doc comment per the stack convention — inline documentation targets the public surface, not every line.
11
+ 5. **Update `docs/.doc-manifest.json`**'s `generated` section, listing the files touched (not a `/docs` file, but still tracked for audit purposes).
12
+
13
+ ## Validation checklist
14
+
15
+ - [ ] Every added/updated comment follows the target stack's exact convention (tag names, block format)
16
+ - [ ] No comment content is invented — parameters, return types, and error conditions match the actual code and the findings
17
+ - [ ] Zero secrets, private passwords, internal IPs, or machine absolute paths added into docblocks/comments
18
+ - [ ] Already-correct existing comments were left untouched
19
+ - [ ] Only public surface area was documented, not every internal line
20
+ - [ ] `docs/.doc-manifest.json`'s `generated` section lists the touched files
@@ -0,0 +1,20 @@
1
+ # Workflow: generate-readme
2
+
3
+ One outcome: a project-root `README.md`, built from `docs/.collected/<target>.md` (target = whole project) and the README shape in `memory/doc-templates.md`. This is the one exception to "everything generated lives under /docs" — README stays at the project root, per convention.
4
+
5
+ ## Steps
6
+
7
+ 1. **Load the findings** from `docs/.collected/<target>.md`. If it doesn't exist for the whole project, stop — see constraint 2 in `SKILL.md`.
8
+ 2. **Pull the README shape** from `memory/doc-templates.md`.
9
+ 3. **Write `README.md`** at the project root: project name/one-line description, install/setup steps (from the runtime & environment findings), usage example, required env vars (with safe placeholders, never real secrets), and a clean relative link to `./docs/README.md` or `docs/` for the full reference. Keep it scannable — this is an entry point, not the full documentation.
10
+ 4. **If a `README.md` already exists**, show a diff-style summary of what would change rather than overwriting silently, and confirm before replacing it.
11
+ 5. **Update `docs/.doc-manifest.json`**'s `generated` section.
12
+
13
+ ## Validation checklist
14
+
15
+ - [ ] `README.md` exists at project root and follows the shape in `memory/doc-templates.md`
16
+ - [ ] Every setup/env-var claim traces to the runtime & environment findings — nothing assumed
17
+ - [ ] Zero real secrets, API keys, or passwords in environment variable tables or examples
18
+ - [ ] Existing `README.md` content was never silently overwritten without confirmation
19
+ - [ ] Links to `docs/` are clean relative links (`./docs/README.md`), never local absolute drive paths or `file:///` URLs
20
+ - [ ] `docs/.doc-manifest.json` updated
@@ -0,0 +1,18 @@
1
+ # Workflow: init-docs
2
+
3
+ One outcome: a working `/docs` scaffold plus a doc manifest, ready for `collect` and `generate` to populate.
4
+
5
+ ## Steps
6
+
7
+ 1. **Detect the stack(s) present** in the project root — PHP (`composer.json`), JS/TS (`package.json`, `tsconfig.json`), and any of React/Vue/Next (framework deps or config files). Note more than one if the project is mixed (e.g., a PHP API with a React front end).
8
+ 2. **Create the folder tree** exactly per `memory/doc-tree.md` — no extra top-level folders, no missing ones. Do not create a folder for a doc type the project doesn't have yet (e.g., skip `docs/api/` for a project with no API surface) — see the "No Empty Structures" note in `doc-tree.md`.
9
+ 3. **Write `docs/.doc-manifest.json`** using the schema in `memory/doc-tree.md`, pre-filled with: detected stack(s), project name (from `composer.json`/`package.json`), and empty `collected` / `generated` tracking sections.
10
+ 4. **Write a placeholder `docs/README.md` index** (one paragraph: what this `/docs` folder contains, and a note that it's generated/maintained by TidyFactor Doc) — this is the doc-site landing page, distinct from the project-root `README.md`.
11
+ 5. **Report** what was created and what stack(s) were detected, and suggest `collect` as the next step.
12
+
13
+ ## Validation checklist
14
+
15
+ - [ ] `/docs` exists with only the subfolders `doc-tree.md` calls for given the detected stack(s) — nothing extra, nothing missing
16
+ - [ ] `docs/.doc-manifest.json` exists, is valid JSON, and matches the schema in `memory/doc-tree.md`
17
+ - [ ] `docs/README.md` (doc-site index) exists and is distinct in content from any project-root `README.md`
18
+ - [ ] No API/inline/guide content was written — this workflow scaffolds only
@@ -0,0 +1,44 @@
1
+ # Workflow: mkdocs
2
+
3
+ One outcome: A fully compiled, statically hosted MkDocs Material documentation portal configured per `memory/mkdocs-config.md`.
4
+
5
+ ---
6
+
7
+ ## Steps
8
+
9
+ 1. **Verify Environment & Dependencies:**
10
+ - Run `python --version` and ensure Python is accessible.
11
+ - Write `requirements.txt` containing `mkdocs-material>=9.5` and `mkdocs-static-i18n>=1.2`.
12
+ - Install dependencies via `pip install -r requirements.txt` if not already installed.
13
+
14
+ 2. **Structure `/docs` Source Tree:**
15
+ - Ensure documentation markdown files live in `docs/` (e.g. `docs/index.md`, `docs/guides/`, `docs/api/`).
16
+ - If bilingual, ensure Arabic files use the `.ar.md` suffix (e.g., `docs/guides/topic.ar.md`).
17
+ - Strip any raw `<div align="center">` wrappers around Markdown headers so Python-Markdown parses them cleanly.
18
+
19
+ 3. **Generate Configuration (`mkdocs.yml`):**
20
+ - Write `mkdocs.yml` at the documentation root using the schema in `memory/mkdocs-config.md`.
21
+ - Configure site name, palette themes (`tidyfactor-light` and `tidyfactor-dark`), PyMdown extensions, and the `nav:` tree mirroring `/docs`.
22
+
23
+ 4. **Inject Luxury Styling & JS Enhancers:**
24
+ - Create `docs/stylesheets/extra.css` with Neo-Brutalist tokens, macOS terminal dots for code blocks, and Arabic font pairings (`El Messiri` + `Tajawal`).
25
+ - Create `docs/javascripts/extra.js` with the GitHub alert transformer and badge bar formatter.
26
+
27
+ 5. **Build & Validate:**
28
+ - Run `mkdocs build --strict` to ensure 0 warnings and 0 broken links.
29
+ - If hosting locally on Apache, inject `.htaccess` with transparent rewrite rules pointing to `site/`.
30
+
31
+ 6. **Report Deployment Instructions:**
32
+ - Report local preview command: `mkdocs serve` (runs at `http://127.0.0.1:8000`).
33
+ - Report production deploy rule: upload the generated contents of `site/` directly into the public server directory.
34
+
35
+ ---
36
+
37
+ ## Validation Checklist
38
+
39
+ - [ ] `mkdocs.yml` exists with valid YAML syntax and complete `nav:` mapping
40
+ - [ ] `docs/index.md` exists and contains no unparsed raw HTML header wrappers
41
+ - [ ] `docs/stylesheets/extra.css` and `docs/javascripts/extra.js` are present
42
+ - [ ] `mkdocs build` completes with **zero errors and zero broken links**
43
+ - [ ] Language switcher functions properly between English and Arabic (if bilingual)
44
+ - [ ] Clean relative links only — no workstation paths (`file:///C:/...`) or hardcoded secrets
@@ -0,0 +1,152 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * build-skill.js — packages the distributable Claude Skill (.skill file)
4
+ * for TidyFactor Doc from the repo's single source of truth.
5
+ */
6
+
7
+ const fs = require("fs");
8
+ const path = require("path");
9
+ const { execFileSync, spawnSync } = require("child_process");
10
+
11
+ const ROOT = path.resolve(__dirname, "..");
12
+ const SKILL_NAME = "tidyfactor-doc";
13
+ const DIST_DIR = path.join(ROOT, "dist");
14
+ const STAGE_DIR = path.join(DIST_DIR, SKILL_NAME);
15
+
16
+ const args = process.argv.slice(2);
17
+ const outFlagIdx = args.indexOf("--out");
18
+ const OUT_FILE =
19
+ outFlagIdx !== -1 && args[outFlagIdx + 1]
20
+ ? path.resolve(ROOT, args[outFlagIdx + 1])
21
+ : path.join(DIST_DIR, `${SKILL_NAME}.skill`);
22
+
23
+ const ROOT_COPIES = [
24
+ "SKILL.md",
25
+ "references",
26
+ "tools",
27
+ "bin",
28
+ "brand.json",
29
+ ".tidyfactor",
30
+ "package.json",
31
+ "AGENTS.md",
32
+ "README.md",
33
+ "README.ar.md",
34
+ "README.fa.md",
35
+ "README.es.md",
36
+ "README.pt.md",
37
+ "README.zh.md",
38
+ "README.de.md",
39
+ "README.fr.md",
40
+ "LICENSE",
41
+ "CHANGELOG.md",
42
+ ];
43
+
44
+ function log(msg) {
45
+ console.log(`[build-skill] ${msg}`);
46
+ }
47
+
48
+ function rmrf(p) {
49
+ if (fs.existsSync(p)) fs.rmSync(p, { recursive: true, force: true });
50
+ }
51
+
52
+ function copyRecursive(src, dest) {
53
+ if (!fs.existsSync(src)) {
54
+ log(` ⚠ skipped (not found): ${path.relative(ROOT, src)}`);
55
+ return;
56
+ }
57
+ fs.cpSync(src, dest, { recursive: true });
58
+ }
59
+
60
+ function zipArchive(stagePath, outFile, cwdDir) {
61
+ log("zipping Claude skill archive...");
62
+ if (fs.existsSync(outFile)) fs.rmSync(outFile);
63
+
64
+ const stageBasename = path.basename(stagePath);
65
+
66
+ // 1. Try native `zip` command
67
+ try {
68
+ execFileSync("zip", ["-r", "-q", outFile, stageBasename], {
69
+ cwd: cwdDir,
70
+ stdio: "inherit",
71
+ });
72
+ return;
73
+ } catch (err) {
74
+ // fallback
75
+ }
76
+
77
+ // 2. Try Python built-in zipfile module
78
+ try {
79
+ const pythonCmd = process.platform === "win32" ? "python" : "python3";
80
+ execFileSync(pythonCmd, ["-m", "zipfile", "-c", outFile, stageBasename], {
81
+ cwd: cwdDir,
82
+ stdio: "inherit",
83
+ });
84
+ return;
85
+ } catch (pyErr) {
86
+ // fallback
87
+ }
88
+
89
+ // 3. Try PowerShell Compress-Archive
90
+ try {
91
+ const tmpZip = outFile.replace(/\.(skill|zip)$/, ".zip");
92
+ if (fs.existsSync(tmpZip)) fs.rmSync(tmpZip);
93
+ const result = spawnSync(
94
+ "powershell",
95
+ [
96
+ "-NoProfile",
97
+ "-NonInteractive",
98
+ "-Command",
99
+ `Compress-Archive -Path "${stagePath}" -DestinationPath "${tmpZip}" -Force`,
100
+ ],
101
+ { stdio: "inherit" }
102
+ );
103
+ if (result.status === 0) {
104
+ if (tmpZip !== outFile) {
105
+ fs.renameSync(tmpZip, outFile);
106
+ }
107
+ return;
108
+ }
109
+ } catch (winErr) {
110
+ // throw cumulative
111
+ }
112
+
113
+ throw new Error("Failed to create zip archive via zip, Python, or PowerShell.");
114
+ }
115
+
116
+ function main() {
117
+ log(`repo root: ${ROOT}`);
118
+ log("cleaning previous build...");
119
+ rmrf(STAGE_DIR);
120
+ fs.mkdirSync(STAGE_DIR, { recursive: true });
121
+
122
+ log("staging single-source-of-truth files from repo root...");
123
+ for (const name of ROOT_COPIES) {
124
+ const src = path.join(ROOT, name);
125
+ const dest = path.join(STAGE_DIR, name);
126
+ copyRecursive(src, dest);
127
+ log(` + ${name}`);
128
+ }
129
+
130
+ const pkg = JSON.parse(fs.readFileSync(path.join(ROOT, "package.json"), "utf8"));
131
+ const versionedOutFile = path.join(DIST_DIR, `${SKILL_NAME}-v${pkg.version}.skill`);
132
+
133
+ fs.mkdirSync(path.dirname(OUT_FILE), { recursive: true });
134
+ zipArchive(STAGE_DIR, OUT_FILE, DIST_DIR);
135
+ fs.copyFileSync(OUT_FILE, versionedOutFile);
136
+
137
+ const sizeKb = (fs.statSync(OUT_FILE).size / 1024).toFixed(1);
138
+ log(`done → ${path.relative(ROOT, OUT_FILE)} (${sizeKb} KB)`);
139
+ log(`✓ Created versioned archive → ${path.relative(ROOT, versionedOutFile)}`);
140
+
141
+ // Auto-sync to Skills-LAB root if located inside Skills-LAB
142
+ const skillLabRoot = path.resolve(ROOT, "..");
143
+ const skillLabTarget = path.join(skillLabRoot, `${SKILL_NAME}.skill`);
144
+ const skillLabVersionedTarget = path.join(skillLabRoot, `${SKILL_NAME}-v${pkg.version}.skill`);
145
+ if (path.basename(skillLabRoot) === "Skills-LAB") {
146
+ fs.copyFileSync(OUT_FILE, skillLabTarget);
147
+ fs.copyFileSync(OUT_FILE, skillLabVersionedTarget);
148
+ log(`✓ Updated Skills-LAB root archives → ${SKILL_NAME}.skill & ${SKILL_NAME}-v${pkg.version}.skill`);
149
+ }
150
+ }
151
+
152
+ main();
@@ -0,0 +1,124 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ validate_skill.py — TidyFactor Doc Release & Integrity Validator.
4
+ Checks:
5
+ 1. SemVer synchronization across package.json, .tidyfactor, brand.json, CHANGELOG.md.
6
+ 2. License consistency (Apache-2.0).
7
+ 3. Existence of all files referenced in SKILL.md.
8
+ 4. Validation checklists in all workflow files.
9
+ 5. Absence of machine-specific absolute paths.
10
+ """
11
+
12
+ import sys
13
+ import os
14
+ import json
15
+ import re
16
+ from pathlib import Path
17
+
18
+ # Ensure UTF-8 output on Windows terminal
19
+ if sys.platform == "win32":
20
+ sys.stdout.reconfigure(encoding="utf-8")
21
+ sys.stderr.reconfigure(encoding="utf-8")
22
+
23
+ def main():
24
+ root = Path(__file__).resolve().parent.parent
25
+ errors = []
26
+
27
+ print("=" * 60)
28
+ print(" RUNNING TIDYFACTOR DOC RELEASE VALIDATION")
29
+ print("=" * 60)
30
+
31
+ # 1. SemVer Synchronization Check
32
+ print("\n[1] Checking SemVer synchronization across metadata...")
33
+ pkg_file = root / "package.json"
34
+ tf_file = root / ".tidyfactor"
35
+ brand_file = root / "brand.json"
36
+ cl_file = root / "CHANGELOG.md"
37
+
38
+ pkg_ver = json.loads(pkg_file.read_text(encoding="utf-8")).get("version") if pkg_file.exists() else None
39
+ tf_ver = json.loads(tf_file.read_text(encoding="utf-8")).get("version") if tf_file.exists() else None
40
+
41
+ brand_data = json.loads(brand_file.read_text(encoding="utf-8")) if brand_file.exists() else {}
42
+ brand_ver = brand_data.get("version") or brand_data.get("meta", {}).get("version")
43
+
44
+ print(f" package.json : {pkg_ver}")
45
+ print(f" .tidyfactor : {tf_ver}")
46
+ print(f" brand.json : {brand_ver}")
47
+
48
+ if not (pkg_ver and tf_ver and brand_ver and pkg_ver == tf_ver == brand_ver):
49
+ errors.append(f"Version mismatch: package.json({pkg_ver}) vs .tidyfactor({tf_ver}) vs brand.json({brand_ver})")
50
+ else:
51
+ print(f" [OK] Version {pkg_ver} synchronized across all JSON metadata.")
52
+
53
+ if cl_file.exists():
54
+ cl_text = cl_file.read_text(encoding="utf-8")
55
+ if f"## [{pkg_ver}]" not in cl_text and f"[{pkg_ver}]" not in cl_text:
56
+ errors.append(f"CHANGELOG.md is missing release entry for version [{pkg_ver}].")
57
+ else:
58
+ print(f" [OK] CHANGELOG.md contains release entry for [{pkg_ver}].")
59
+ else:
60
+ errors.append("Missing CHANGELOG.md.")
61
+
62
+ # 2. License check
63
+ print("\n[2] Checking license consistency...")
64
+ license_file = root / "LICENSE"
65
+ if license_file.exists():
66
+ print(" [OK] LICENSE file exists (Apache-2.0).")
67
+ else:
68
+ errors.append("Missing LICENSE file.")
69
+
70
+ # 3. Check referenced files in SKILL.md
71
+ print("\n[3] Checking SKILL.md referenced files exist on disk...")
72
+ skill_md = root / "SKILL.md"
73
+ if skill_md.exists():
74
+ content = skill_md.read_text(encoding="utf-8")
75
+ # filter out wildcards like generate-*.md
76
+ refs = re.findall(r'(?:references|memory|commands|workflows)/[a-zA-Z0-9_\-\./]+(?:\.md|\.json)?', content)
77
+ for ref in sorted(set(refs)):
78
+ if "*" in ref or ref.endswith("-"):
79
+ continue
80
+ target = root / ref
81
+ if not target.exists() and not (root / "references" / ref).exists():
82
+ if not (root / ref).is_dir() and not (root / "references" / ref).is_dir():
83
+ errors.append(f"SKILL.md references non-existent file: {ref}")
84
+ else:
85
+ print(f" [OK] Found {ref}")
86
+ else:
87
+ errors.append("Missing SKILL.md.")
88
+
89
+ # 4. Check workflow validation checklists
90
+ print("\n[4] Checking workflow checklists...")
91
+ wf_dir = root / "references" / "workflows"
92
+ if wf_dir.exists():
93
+ for wf in wf_dir.glob("*.md"):
94
+ txt = wf.read_text(encoding="utf-8").lower()
95
+ if "validation checklist" not in txt:
96
+ errors.append(f"Workflow {wf.name} missing '## Validation checklist'")
97
+ else:
98
+ print(f" [OK] {wf.name} has Validation checklist.")
99
+
100
+ # 5. Audit for leaked machine paths
101
+ print("\n[5] Auditing for leaked machine-specific absolute paths...")
102
+ for ext in ["*.md", "*.json", "*.js", "*.py"]:
103
+ for file in root.rglob(ext):
104
+ if any(part in file.parts for part in [".git", "node_modules", "dist"]):
105
+ continue
106
+ text = file.read_text(encoding="utf-8", errors="ignore")
107
+ if re.search(r'[A-Za-z]:\\[Users|wamp64|Dev\-Studio]', text, re.IGNORECASE):
108
+ if file.name not in ["release_suite.py", "audit_all_skills.py"]:
109
+ errors.append(f"Machine-specific absolute path found in: {file.relative_to(root)}")
110
+
111
+ print("\n" + "=" * 60)
112
+ if errors:
113
+ print(f"[FAIL] {len(errors)} validation error(s) found:")
114
+ for err in errors:
115
+ print(f" - {err}")
116
+ print("=" * 60)
117
+ sys.exit(1)
118
+ else:
119
+ print("[SUCCESS] ALL SKILL INTEGRITY CHECKS PASSED!")
120
+ print("=" * 60)
121
+ sys.exit(0)
122
+
123
+ if __name__ == "__main__":
124
+ main()