From 810a893382ee943f43dc7389756fa07f7731efa5 Mon Sep 17 00:00:00 2001 From: Marcos Muino Date: Mon, 10 Aug 2026 15:19:09 +0200 Subject: [PATCH] =?UTF-8?q?docs:=20global=20review=20=E2=80=94=20dedup,=20?= =?UTF-8?q?tighter=20descriptions,=20gaps,=20CI=20validation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add CONTRIBUTING.md with a content-ownership map (one owning skill per concept, pointers elsewhere) and the JS/Python mirroring rule - Add CI: skills-ref validation for every skill plus a check that fcode-javascript and fcode-python section headings stay in sync - Rewrite fcode-cli and fcode-forms frontmatter descriptions, which had grown into keyword lists through per-feature appends - Deduplicate repeated explanations into their owners: the settings-form pre-fill rules (fcode-forms owns; custom-app-linear points), the FACTORIAL_TOKEN OAuth procedure (fcode-cli owns), the webhookAuth non-inheritance rationale (fcode-cli owns), the silent version_tag fallback (stated twice inside fcode-forms), and the stable-alias rationale in fcode-agent - Document APIs the examples use but the language skills omitted: fcode.storage.createSignedUrl / create_signed_url, fcode.team.slug, and the schedule allowConcurrentExecutions option - Add the built-in fcode.sendMail option to the fcode-agent example interaction, which predated it - fcode-examples: clarify the fcode-forms module vs skill name collision and state that references omit routine logging (fcode-logs still applies) - Align custom-app-linear's credential field with the isSensitive-only guidance; fix a broken paragraph and a sample-schema typo Co-Authored-By: Claude Fable 5 --- .github/workflows/validate.yml | 39 +++++++++++++++++ CONTRIBUTING.md | 42 +++++++++++++++++++ README.md | 3 ++ skills/fcode-agent/SKILL.md | 12 +++--- .../references/example-interaction.md | 8 ++-- skills/fcode-cli/SKILL.md | 2 +- skills/fcode-examples/SKILL.md | 8 ++++ .../references/custom-app-linear.md | 20 ++++----- .../references/integration-acme.md | 13 +++--- skills/fcode-forms/SKILL.md | 11 ++--- skills/fcode-javascript/SKILL.md | 7 ++++ .../assets/parametersSchema.sample.json | 2 +- skills/fcode-python/SKILL.md | 8 ++++ 13 files changed, 137 insertions(+), 38 deletions(-) create mode 100644 .github/workflows/validate.yml create mode 100644 CONTRIBUTING.md diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml new file mode 100644 index 0000000..b788853 --- /dev/null +++ b/.github/workflows/validate.yml @@ -0,0 +1,39 @@ +name: Validate skills + +on: + pull_request: + push: + branches: [main] + +permissions: + contents: read + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: 22 + + - name: Validate every skill with skills-ref + run: | + for dir in skills/*/; do + npx -y skills-ref validate "$dir" + done + + - name: Check fcode-javascript and fcode-python stay mirrored + run: | + extract_headings() { + awk '/^```/{in_code=!in_code; next} !in_code && /^##/' "$1" + } + if ! diff \ + <(extract_headings skills/fcode-javascript/SKILL.md) \ + <(extract_headings skills/fcode-python/SKILL.md); then + echo "::error::fcode-javascript and fcode-python section headings diverged — semantic changes must land in both skills." + exit 1 + fi diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..1eae4aa --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,42 @@ +# Contributing + +## Content ownership + +Every concept has exactly **one owning skill** that holds its full explanation. +Any other skill that needs the concept gets at most a one-line gotcha plus a +pointer to the owner (`see fcode-`). When documenting a new platform +feature, write it up once in the owner and add pointers elsewhere — do not +repeat the explanation across skills. + +| Concept | Owner | +|---|---| +| Platform model: processes, modules, execution context, variables & inheritance model, datastore vs storage, versioning & alias model | `fcode-core-concepts` | +| CLI commands and flow, `metadata.json` / `team.json` field reference, webhook auth mechanics, the three variables files, `variables.meta.json`, the `FACTORIAL_TOKEN` OAuth procedure, `version_tag` on URLs | `fcode-cli` | +| Language usage: runtime helpers, code snippets, logging, dependencies, return values | `fcode-javascript` / `fcode-python` | +| `parametersSchema.json` field types, widgets, validation | `fcode-json-schema` | +| Form embedding, themes, access restriction, pre-render / pre-fill contract, i18n, multi-step | `fcode-forms` | +| Agent working method, MCP tools | `fcode-agent` | +| Worked, adaptable examples (code, not rules — rules live with their owner) | `fcode-examples` | + +## The JavaScript / Python twins + +`fcode-javascript` and `fcode-python` are deliberate mirrors: the same +sections, the same semantics, language-specific idiom. **Every semantic change +must land in both files.** CI fails when their section headings diverge. + +## Validation + +CI validates every skill with +[`skills-ref`](https://github.com/agentskills/agentskills/tree/main/skills-ref) +on each pull request. Run it locally with: + +```bash +for dir in skills/*/; do npx skills-ref validate "$dir"; done +``` + +## Frontmatter descriptions + +The `description` field is the routing surface agents use to pick a skill. +Keep it to what the skill covers plus when to use it — a couple of sentences +with the trigger words that matter, not an exhaustive feature list. Don't +append keywords to it with every feature PR. diff --git a/README.md b/README.md index 20c10f2..4470f51 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,9 @@ Each skill is a directory under `skills/` containing a `SKILL.md` npx skills-ref validate skills/fcode-javascript ``` +CI validates every skill on each pull request. Content-ownership rules for +contributors are in [CONTRIBUTING.md](CONTRIBUTING.md). + ## License [MIT](LICENSE) diff --git a/skills/fcode-agent/SKILL.md b/skills/fcode-agent/SKILL.md index 747fc45..02fc906 100644 --- a/skills/fcode-agent/SKILL.md +++ b/skills/fcode-agent/SKILL.md @@ -53,9 +53,8 @@ Work in small steps, confirming at each one. Share results. - When a secret value is needed for discovery/testing, ask the user for it — or, if they prefer not to share it, ask them to put it in - `variables.local.env` themselves (see `fcode-cli`). For `FACTORIAL_TOKEN`, - point them to the OAuth flow in the Factorial Code app details page and the - copy dropdown option in the OAuth Dev app. + `variables.local.env` themselves. For `FACTORIAL_TOKEN`, follow the OAuth + procedure in `fcode-cli`. - Remind the user that local secret values aren't pushed — they must create those variables manually in the remote demo environment (except `FACTORIAL_TOKEN`, which is auto-populated remotely). @@ -73,10 +72,9 @@ Then offer next steps: scheduling, webhooks (public, inheriting the workspace `webhookAuth` configuration, or with their own header and team variable), a form (public, or restricted to Factorial users), or exposing the process as an MCP tool — and pushing to cloud when ready. Pushing only updates the current code: -consumers pinned to the `stable` alias (as webhook URLs and form embeds should -be) are unaffected until a release moves the alias, which is done from the web -UI — don't create workspace versions or move `stable` unless explicitly asked -(see `fcode-core-concepts` and `fcode-cli`). +consumers pinned to the `stable` alias are unaffected until a release moves the +alias — don't create workspace versions or move `stable` unless explicitly +asked (model in `fcode-core-concepts`, commands in `fcode-cli`). ## Creating MCP tools diff --git a/skills/fcode-agent/references/example-interaction.md b/skills/fcode-agent/references/example-interaction.md index e27ca2c..0fdcd67 100644 --- a/skills/fcode-agent/references/example-interaction.md +++ b/skills/fcode-agent/references/example-interaction.md @@ -9,12 +9,14 @@ notifications with order details to customers" **Agent:** "I'll help you create this integration. First, for sending emails, would you prefer: -1. **Email service** (SendGrid, AWS SES) — easier setup, better deliverability -2. **Direct SMTP** — more control, no third-party needed +1. **Built-in `fcode.sendMail`** — zero setup, but a fixed From address and a + cap of 3 emails per execution (see `fcode-javascript` / `fcode-python`) +2. **Email service** (SendGrid, AWS SES) — custom From, better for volume +3. **Direct SMTP** — more control, no third-party needed Which approach?" -**User:** "SendGrid" +**User:** "SendGrid — there can be many orders per run" **Agent:** "Perfect! Here's the plan: diff --git a/skills/fcode-cli/SKILL.md b/skills/fcode-cli/SKILL.md index 93b38dd..b2fd011 100644 --- a/skills/fcode-cli/SKILL.md +++ b/skills/fcode-cli/SKILL.md @@ -1,6 +1,6 @@ --- name: fcode-cli -description: Use the Factorial Code CLI (fcode) for local development and cloud sync — the pull → add → dependencies:install → run → push flow, the local webhook/forms server (fcode http), workspace versions and aliases (team:versions, team:aliases, the stable alias, version_tag on webhook URLs), when to run each command, --force safety, worked examples, and the workspace config files (process metadata.json with webhook authMode/auth and form authMode/appRole, team.json with the workspace webhookAuth and versions/aliases, the three variables files including inherited variables from parent workspaces, variables.meta.json). Use when running fcode CLI commands, testing a Factorial Code process locally, deploying/syncing Factorial Code (fcode) resources to the cloud, managing workspace versions or aliases, overriding an inherited team variable, or activating and protecting a process's webhook or form settings. +description: Use the Factorial Code CLI (fcode) for local development and cloud sync — the pull → add → run → push flow, the local webhook/forms server (fcode http), workspace versions and aliases (the stable alias, version_tag), and the workspace config files (process metadata.json, team.json, the three variables files, variables.meta.json). Use when running fcode CLI commands, testing a process locally, syncing to the cloud, managing versions or aliases, overriding an inherited team variable, or configuring a process's webhook or form settings. license: MIT metadata: category: factorial-code diff --git a/skills/fcode-examples/SKILL.md b/skills/fcode-examples/SKILL.md index e62fee1..abbcdb4 100644 --- a/skills/fcode-examples/SKILL.md +++ b/skills/fcode-examples/SKILL.md @@ -65,6 +65,14 @@ reimplement them: Integration apps inherit base-integration-app (which inherits base-app); custom apps inherit base-app directly. +Note: the `fcode-forms` **module** above (form-schema builders you +`fcode.import`) is unrelated to the `fcode-forms` **skill**, which documents +embedding forms on webpages. + +The references keep code compact and omit routine logging; real +implementations should log verbosely through the shared `fcode-logs` module +(see `fcode-javascript` / `fcode-python`). + ## Language variants Every sample exists in JavaScript (Node.js v22) and Python (3.13) with diff --git a/skills/fcode-examples/references/custom-app-linear.md b/skills/fcode-examples/references/custom-app-linear.md index c714997..f9c7028 100644 --- a/skills/fcode-examples/references/custom-app-linear.md +++ b/skills/fcode-examples/references/custom-app-linear.md @@ -124,9 +124,9 @@ module.exports = { LinearApiClient }; ## Setup step 1 — validate credentials, chain to step 2 -A minimal form (one password field, `"isSensitive": true`, -`"ui:widget": "password"`). The process validates the key with a real API -call, persists it as a team variable, and chains by returning +A minimal form (one credential field with `"isSensitive": true`, which renders +a password input — see `fcode-json-schema`). The process validates the key +with a real API call, persists it as a team variable, and chains by returning `nextProcessId`: ```javascript @@ -380,16 +380,12 @@ await fcode.datastore.set( ); ``` -Two rules this encodes: +The rules this encodes — a secret is reported, never echoed; blank on submit +means "keep the current value"; the credential field is not `required`; a +pre-render defaults missing state instead of throwing — are the pre-fill +contract in `fcode-forms` (`references/advanced.md`). -- **Secrets are reported, never echoed.** The schema response travels over HTTP - and lands in the browser DOM; `authMode: FACTORIAL` limits *who* sees it, not - *where it goes*. Show "configured", never the value — not even masked, since a - masked value then has to be told apart from a real one on submit. -- **A pre-render failure fails the form load.** Default missing state (`?? 1`, - `|| "{}"`) instead of throwing. - -That second rule changes the install-flow snippet above. Throwing on a missing +The no-throw rule changes the install-flow snippet above. Throwing on a missing `LINEAR_API_KEY` is fine while the form is only ever reached from step 1, which just stored it — but the moment the same form is also the `SETTINGS` screen, that throw makes it unopenable in precisely the state where the user needs it to enter diff --git a/skills/fcode-examples/references/integration-acme.md b/skills/fcode-examples/references/integration-acme.md index a80dce8..81b702d 100644 --- a/skills/fcode-examples/references/integration-acme.md +++ b/skills/fcode-examples/references/integration-acme.md @@ -88,9 +88,8 @@ Factorial-triggered webhook expects `FACTORIAL_CHALLENGE_TOKEN` in the } ``` -Set `webhookAuth` in **every** app workspace: it is not inherited through -`parentTeamSlugs`, because auth resolves against the workspace addressed in the -webhook URL rather than the one owning the code. Field reference in `fcode-cli`. +Set `webhookAuth` in **every** app workspace — it is not inherited through +`parentTeamSlugs` (why, and field reference, in `fcode-cli`). The platform authenticates the caller before the process runs, so there is nothing to check in code and the body stays minimal: @@ -264,10 +263,10 @@ Vendor variables follow the `VENDOR__KEY` convention (matches `variableName()`). `FACTORIAL_TOKEN` is auto-populated in the remote environment — don't create -it there. For local runs, obtain it via the OAuth flow in the Factorial Code -app details page (copy dropdown in the OAuth Dev app) and put it in -`variables.local.env`. Other sensitive variables must be created manually in -the remote demo environment — local values aren't pushed (see `fcode-cli`). +it there. For local runs, obtain it via the OAuth flow and put it in +`variables.local.env` (procedure in `fcode-cli`). Other sensitive variables +must be created manually in the remote demo environment — local values aren't +pushed (see `fcode-cli`). ## Adapting to a real vendor — checklist diff --git a/skills/fcode-forms/SKILL.md b/skills/fcode-forms/SKILL.md index 960d834..337ab47 100644 --- a/skills/fcode-forms/SKILL.md +++ b/skills/fcode-forms/SKILL.md @@ -1,6 +1,6 @@ --- name: fcode-forms -description: Embed a Factorial Code process's input-parameter form on a webpage — the three embed methods (data attributes, Fcode.initForm, FcodeForm React component) addressed by team + process slug and pinned to a process version (the stable alias, data-fcode-form-process-version / processVersion), restricting access with authMode, driving behavior from the process return value (message/formErrors/redirect/nextProcessId), styling and the two themes (the SDK stylesheet theme vs the f0 theme for React apps inside Factorial), i18n, multi-step flows, pre-rendering current values into install/settings forms, and automatic file uploads to Storage. Use when embedding, configuring, styling, theming, restricting access to, version-pinning, pre-filling, or wiring up submission callbacks for a Factorial Code (fcode) form. +description: Embed a Factorial Code process's input-parameter form on a webpage — the three embed methods (data attributes, Fcode.initForm, the FcodeForm React component), version pinning to the stable alias, access restriction (authMode), driving behavior from the process return value, styling and themes (including the f0 theme inside Factorial), i18n, multi-step flows, pre-filling current values, and file uploads. Use when embedding, configuring, styling, restricting, version-pinning, pre-filling, or wiring up a Factorial Code (fcode) form. license: MIT metadata: category: factorial-code @@ -24,10 +24,8 @@ handled in-page (messages, redirects, callbacks). For the schema itself, see (`data-fcode-form-process-version="stable"` / `processVersion: "stable"`). An unpinned form runs the current version, so every `fcode push` changes it immediately. Details below. -- **An unknown version or alias doesn't fail the form** — it silently loads and - submits on the current version, with only a server-side warning. A typo in the - version attribute is invisible in the page; check the execution's version when - a submission behaves unexpectedly. +- **An unknown version or alias doesn't fail the form** — it silently runs the + current version (see "Pin the form to a version"). - **The `Forms` flag must be enabled** — on the process Dashboard, or via `"form": { "enabled": true }` in the process's `metadata.json` + `fcode push` — or the embed won't render. @@ -288,8 +286,7 @@ mainly for form-level options like `ui:submitButtonOptions`. For secret inputs prefer `"isSensitive": true` on the property — it renders a password widget automatically; see `fcode-json-schema`.) -On -submit the file is **uploaded to Storage before the process starts**, and the +On submit the file is **uploaded to Storage before the process starts**, and the parameter arrives as an `fcode.storage://…` reference (an **array** if multiple files allowed). Strip the prefix to download: diff --git a/skills/fcode-javascript/SKILL.md b/skills/fcode-javascript/SKILL.md index 4829c77..ee9a287 100644 --- a/skills/fcode-javascript/SKILL.md +++ b/skills/fcode-javascript/SKILL.md @@ -59,6 +59,9 @@ const { id: processId, name: processName } = fcode.execution.process; const { id: scheduleId } = fcode.execution.schedule; // when run from a schedule const timezone = fcode.execution.timezone; +// Workspace (team) metadata +const teamSlug = fcode.team.slug; + // Environment variables (secrets/config) const apiKey = process.env.API_KEY; // or fcode.env.API_KEY @@ -123,6 +126,9 @@ await fcode.storage.upload("path/myfile.txt", fs.createReadStream(localPath)); const files = await fcode.storage.list(); const stream = await fcode.storage.download("path/myfile.txt"); stream.pipe(fs.createWriteStream(localPath)); +// Signed download URL — { url, expiresAt }. A real HTTPS link in the cloud, +// a file:// URL locally (same shape, no special-casing). +const signed = await fcode.storage.createSignedUrl("path/myfile.txt"); await fcode.storage.delete("path/myfile.txt"); ``` @@ -161,6 +167,7 @@ await fcode.variables.delete("API_KEY"); // no-op on an inherited variabl const schedule = await fcode.schedule.create("my-process", { cron: "0 0 6 * * SUN", // or: dateTime: "2026-04-24T12:30:00.000" input: { parameters: { foo: "bar" } }, + allowConcurrentExecutions: false, // optional }); const schedules = await fcode.schedule.list({ processId: fcode.execution.process.id, diff --git a/skills/fcode-json-schema/assets/parametersSchema.sample.json b/skills/fcode-json-schema/assets/parametersSchema.sample.json index 83730ef..dae7a55 100644 --- a/skills/fcode-json-schema/assets/parametersSchema.sample.json +++ b/skills/fcode-json-schema/assets/parametersSchema.sample.json @@ -11,7 +11,7 @@ "onePasswordField": { "title": "One password field", "type": "string", - "description": "Password shoul be: \n 1. At least 12 characters long \n 2. Include a combination of uppercase and lowercase letters \n 3. At least one special character such as @, #, $, %", + "description": "Password should be: \n 1. At least 12 characters long \n 2. Include a combination of uppercase and lowercase letters \n 3. At least one special character such as @, #, $, %", "isSensitive": true, "ui": { "ui:placeholder": "Use a secure password" diff --git a/skills/fcode-python/SKILL.md b/skills/fcode-python/SKILL.md index 5713959..05fb81f 100644 --- a/skills/fcode-python/SKILL.md +++ b/skills/fcode-python/SKILL.md @@ -58,6 +58,9 @@ process_id = fcode.execution.process.id schedule_id = fcode.execution.schedule.id # when run from a schedule timezone = fcode.execution.timezone +# Workspace (team) metadata +team_slug = fcode.team.slug + # Environment variables (secrets/config) api_key = os.getenv("API_KEY") # or fcode.env.API_KEY @@ -127,6 +130,10 @@ content = fcode.storage.download("path/myfile.txt") with open(local_path, "wb") as f: f.write(content) +# Signed download URL — { "url", "expiresAt" }. A real HTTPS link in the +# cloud, a file:// URL locally (same shape, no special-casing). +signed = fcode.storage.create_signed_url("path/myfile.txt") + fcode.storage.delete("path/myfile.txt") ``` @@ -163,6 +170,7 @@ schedule = fcode.schedule.create( "my-process", cron="0 0 6 * * SUN", # or: date_time="2026-04-24T12:30:00.000" parameters={"foo": "bar"}, + allow_concurrent_executions=False, # optional ) schedules = fcode.schedule.list(process_id=fcode.execution.process.id) fcode.schedule.pause(schedule["id"])