diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..c6ddcf410 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: true +contact_links: + - name: Contributor getting started (DARC, SKEEP, lanes, labels) + url: https://skainet-developers.github.io/SKaiNET/skainet/contributing/getting-started.html + about: Read this first if you want to pick up a task — it explains the two workflows and how issues are labelled. + - name: Issue taxonomy + url: https://skainet-developers.github.io/SKaiNET/skainet/contributing/issue-taxonomy.html + about: What every label, title prefix and template means, and how to decompose a feature into lanes. diff --git a/.github/ISSUE_TEMPLATE/darc_lane_task.md b/.github/ISSUE_TEMPLATE/darc_lane_task.md new file mode 100644 index 000000000..973f80291 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/darc_lane_task.md @@ -0,0 +1,43 @@ +--- +name: "DARC Lane Task (sub-issue)" +about: "One independently claimable task split out of a DARC feature — a single lane, a single skill, an honest size" +title: "[Lane N · skill] Feature — what this task produces" +labels: sub-issue +assignees: "" +--- + + + +Sub-issue of # (). + +**Lane:** +**Skill needed:** +**Size:** () +**Blocked by:** <#issue, or "nothing"> + +## What to do + +1. +2. +3. + +## Acceptance + +- [ ] +- [ ] +- [ ] Result reported back on the parent issue + +## Notes + + diff --git a/.github/ISSUE_TEMPLATE/skeep_tracking.md b/.github/ISSUE_TEMPLATE/skeep_tracking.md new file mode 100644 index 000000000..be02d4b45 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/skeep_tracking.md @@ -0,0 +1,45 @@ +--- +name: "SKEEP Proposal (tracking issue)" +about: "Track a durable design record — a public API, DSL, storage, runtime, compiler, or compatibility decision" +title: "[SKEEP-NNN]: " +labels: skeep, tracking +assignees: "" +--- + + + +**Proposal document:** `docs/modules/skeep/pages/NNN-short-title.adoc` () +**Status:** Draft +**Branch:** `feature/skeep-NNN-short-title` + +## Trigger + + + +## Summary + + + +## Related DARC features + + + +## Sub-issues + + + +## Status upkeep + +- [ ] Proposal registered in `docs/modules/skeep/nav.adoc` and the "Current Proposals" table +- [ ] Maintainer moved status to `Accepted` +- [ ] Implementation PR(s) linked here **and** flipped the proposal's `Status:` to `Implemented` diff --git a/.github/labels.txt b/.github/labels.txt new file mode 100644 index 000000000..e735a4b85 --- /dev/null +++ b/.github/labels.txt @@ -0,0 +1,36 @@ +# SKaiNET issue label taxonomy — source of truth for `.github/scripts/sync-labels.sh`. +# Format: name|hex-color|description (lines starting with # are ignored) +# Documented in docs/modules/ROOT/pages/contributing/issue-taxonomy.adoc. +# +# --- Structure: how an issue relates to other issues ------------------------- +tracking|ededed|Parent/tracking issue with sub-issues +sub-issue|ededed|Sub-issue of a tracking issue +darc|a2eeef|Feature driven through the DARC workflow (Document / Assess / Research / Code) +skeep|5319e7|SKEEP proposal tracking issue (durable design record) +# +# --- DARC phase: which phase of the workflow the task belongs to ------------ +assessment|e99695|Assessment task (DARC: A) +research|c2e0c6|Research and evaluation tasks (DARC: R) +coding|006b75|Implementation task (DARC: C) +documentation|0075ca|Improvements or additions to documentation (DARC: D) +# +# --- Skill: what a contributor needs to know to pick the task up ------------- +skill:numerics|1d76db|PyTorch/NumPy/math background, no Kotlin required +skill:kotlin-core|1d76db|Kotlin implementation in commonMain +skill:android|1d76db|Android target/build/kernel work +skill:ios|1d76db|iOS / Kotlin-Native-Apple target work +skill:native|1d76db|Kotlin/Native (Linux, macOS) or FFM kernel work +skill:js|1d76db|JS / Wasm target work +skill:docs|1d76db|AsciiDoc / technical writing +skill:review|1d76db|DARC review; must not be the task's implementer +skill:design|5319e7|SKEEP authorship; architectural / API-shape judgement +# +# --- Size: honest effort estimate ------------------------------------------ +size:xs|d4f7d4|Under 1 hour +size:s|b6ebb6|A few hours +size:m|f9e79f|1-2 days +size:l|f5b7a0|3+ days; likely needs its own design discussion +# +# --- Entry point ----------------------------------------------------------- +good first issue|7057ff|Good for newcomers; no prior SKaiNET codebase knowledge assumed +help wanted|008672|Extra attention is needed diff --git a/.github/scripts/sync-labels.sh b/.github/scripts/sync-labels.sh new file mode 100755 index 000000000..835e628bc --- /dev/null +++ b/.github/scripts/sync-labels.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +# Idempotently create/update the GitHub labels declared in .github/labels.txt. +# +# Usage: +# .github/scripts/sync-labels.sh # against the current repo +# .github/scripts/sync-labels.sh -R owner/repo # against another repo +# DRY_RUN=1 .github/scripts/sync-labels.sh # print what would run +# +# Requires `gh` authenticated with triage (or higher) permission on the repo. +# Existing labels are updated in place (--force); labels that are not in +# labels.txt are left untouched — this script never deletes anything. +set -euo pipefail + +here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +labels_file="${LABELS_FILE:-$here/../labels.txt}" +repo_args=("$@") + +if ! command -v gh >/dev/null 2>&1; then + echo "error: gh CLI not found" >&2 + exit 1 +fi + +count=0 +while IFS='|' read -r name color description; do + # skip comments and blank lines + case "$name" in ''|\#*) continue ;; esac + name="${name%"${name##*[![:space:]]}"}" # rtrim + color="${color//[[:space:]]/}" + description="${description#"${description%%[![:space:]]*}"}" # ltrim + cmd=(gh label create "$name" --color "$color" --description "$description" --force "${repo_args[@]}") + if [[ "${DRY_RUN:-0}" == "1" ]]; then + printf '%q ' "${cmd[@]}"; echo + else + "${cmd[@]}" + fi + count=$((count + 1)) +done < "$labels_file" + +echo "synced $count labels from $labels_file" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f9442fe70..55e463c02 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,6 +4,47 @@ SKaiNET uses the Gitflow branching model described in [GITFLOW.adoc](GITFLOW.adoc). Keep ordinary fixes small, focused, and easy to review. +## Two Processes: DARC and SKEEP + +Non-trivial work in SKaiNET goes through one of two processes — sometimes +both. They answer different questions: + +| | DARC | SKEEP | +|---|---|---| +| **Question** | Is *this feature* the right thing to build, and is it built to the documented design? | What is the durable *shape of the codebase* going forward? | +| **Unit of work** | One operator, metric, layer, format reader, kernel strategy | One architectural decision: public API, DSL syntax, storage model, runtime/compiler integration, compatibility policy | +| **Phases / states** | Document → Assess → Research → Code (cyclical) | Draft → Accepted → Implemented (or Superseded / Rejected) | +| **Artifact** | Feature issue (`.github/ISSUE_TEMPLATE/darc_feature_request.md`) decomposed into lane sub-issues; for operators, a doc partial + `@DarcValidated` | `docs/modules/skeep/pages/NNN-short-title.adoc` + a tracking issue (`.github/ISSUE_TEMPLATE/skeep_tracking.md`) | +| **Sign-off** | A reviewer who is not the implementer | A maintainer, by moving the `Status:` field | + +**Which one?** If the change trips a SKEEP trigger (next section), write the +SKEEP first and have the DARC feature issue link to it. If it doesn't, but a +maintainer six months from now would want to know *why* the change is shaped +the way it is, it's DARC. Typos, obvious one-line fixes, dependency bumps and +test-only changes need neither. + +Full text: [Getting started as a contributor](https://skainet-developers.github.io/SKaiNET/skainet/contributing/getting-started.html) +and [DARC: advanced contribution workflow](https://skainet-developers.github.io/SKaiNET/skainet/contributing/darc-workflow.html) +(sources under `docs/modules/ROOT/pages/contributing/`). + +## Finding Work: Issue Taxonomy + +A DARC feature is one parent issue (`tracking`, `darc`) plus one native +sub-issue per *lane* — numerics research, Kotlin core, per-platform +verification, ground-truth/CI, docs, review. Every sub-issue carries exactly +one `skill:*` label (`numerics`, `kotlin-core`, `android`, `ios`, `native`, +`js`, `docs`, `review`, `design`), one `size:*` label (`xs` < 1 h, `s` a few +hours, `m` 1–2 days, `l` 3+ days), and a DARC phase label (`documentation`, +`assessment`, `research`, `coding`). `good first issue` means no prior +SKaiNET codebase knowledge is assumed. + +Useful searches: `is:open label:"good first issue"`, +`is:open label:skill:android label:size:xs`, `is:open label:tracking label:darc`. + +The label set is declared in `.github/labels.txt` and applied with +`.github/scripts/sync-labels.sh`; the full reference is +[Issue taxonomy](https://skainet-developers.github.io/SKaiNET/skainet/contributing/issue-taxonomy.html). + ## When to Write an SKEEP SKEEP stands for SKaiNET Evolution and Enhancement Process. It is the @@ -21,7 +62,10 @@ Write an SKEEP when a change affects: You usually do not need an SKEEP for local bug fixes, internal refactors, dependency bumps, test-only changes, typo fixes, or implementation details that -do not affect user-visible behavior. +do not affect user-visible behavior. Additive features that sit behind an +existing interface (a new metric implementing `Metric`, a new op on an existing +backend) are DARC features, not SKEEPs — unless building them forces one of the +triggers above. ## SKEEP Procedure @@ -38,13 +82,20 @@ do not affect user-visible behavior. `docs/modules/skeep/pages/index.adoc`. 6. Start new proposals with `Status: Draft`. Use `Accepted`, `Implemented`, `Superseded`, or `Rejected` only when maintainers have made that decision. -7. Include the standard sections: summary, motivation, proposed design, + The PR that ships the implementation must also flip the status to + `Implemented` — a proposal whose code has shipped but whose status still + says `Draft` is worse than no status field at all. +7. Open a tracking issue from `.github/ISSUE_TEMPLATE/skeep_tracking.md` + (title `[SKEEP-NNN]: …`, labels `skeep`, `tracking`) and put its link in + the proposal's `Tracking issue:` header. The proposal is the durable + record; the issue is where day-to-day coordination happens. +8. Include the standard sections: summary, motivation, proposed design, compatibility and migration notes, rollout plan, acceptance criteria, risks, open questions, and references. -8. If the proposal depends on external language or platform features, link the +9. If the proposal depends on external language or platform features, link the relevant upstream documents and call out stability or compiler-flag requirements. -9. Keep implementation PRs connected to the SKEEP. The proposal explains the +10. Keep implementation PRs connected to the SKEEP. The proposal explains the shape of the decision; code changes prove and ship it. SKEEP files are part of the Antora docs component. The module is registered in diff --git a/README.md b/README.md index 28a48ad5b..2540c6f93 100644 --- a/README.md +++ b/README.md @@ -124,14 +124,26 @@ SKaiNET is a modular ecosystem. While this repository contains the core engine, ## Contributing and Design Proposals +**First time here?** Three links are all you need: + +- 🚀 [Getting started as a contributor](https://skainet-developers.github.io/SKaiNET/skainet/contributing/getting-started.html) — the two workflows in one page, and how to claim a task. +- 🟣 [Open good first issues](https://github.com/SKaiNET-developers/SKaiNET/issues?q=is%3Aopen+label%3A%22good+first+issue%22) — filter further by what you know: [`skill:android`](https://github.com/SKaiNET-developers/SKaiNET/issues?q=is%3Aopen+label%3Askill%3Aandroid), [`skill:numerics` (no Kotlin)](https://github.com/SKaiNET-developers/SKaiNET/issues?q=is%3Aopen+label%3Askill%3Anumerics), [`skill:docs`](https://github.com/SKaiNET-developers/SKaiNET/issues?q=is%3Aopen+label%3Askill%3Adocs), or by size: [`size:xs`](https://github.com/SKaiNET-developers/SKaiNET/issues?q=is%3Aopen+label%3Asize%3Axs). +- 🏷️ [Issue taxonomy](https://skainet-developers.github.io/SKaiNET/skainet/contributing/issue-taxonomy.html) — what every label and `[Lane N · skill]` title prefix means. + Small fixes can go straight through the normal contribution flow described in [CONTRIBUTING.md](CONTRIBUTING.md) and [GITFLOW.adoc](GITFLOW.adoc). -Use a SKEEP when a change affects public APIs, DSL syntax, tensor semantics, -compiler/runtime integration, storage behavior, compatibility policy, or other -decisions that need a durable design record. SKEEP files live under -`docs/modules/skeep/pages/` and use three-digit numbering, starting with -`001`. +Non-trivial work goes through one of two processes: + +- **DARC** (Document / Assess / Research / Code) for *one feature* — a new + operator, metric, layer, format reader, or kernel strategy. A feature is one + parent issue plus skill-labelled sub-issues ("lanes"). See the + [DARC workflow](https://skainet-developers.github.io/SKaiNET/skainet/contributing/darc-workflow.html). +- **SKEEP** (SKaiNET Evolution and Enhancement Process) for *one architectural + decision* — public APIs, DSL syntax, tensor semantics, compiler/runtime + integration, storage behavior, compatibility policy. SKEEP files live under + `docs/modules/skeep/pages/` with three-digit numbering. See the + [SKEEP index](https://skainet-developers.github.io/SKaiNET/skainet/skeep/index.html). --- @@ -329,9 +341,9 @@ See [CHANGELOG.md](CHANGELOG.md) for full release notes, including every prior r We love contributions! Whether it's a new operator, documentation, or a bug fix: -1. Read our [Contribution Guide](CONTRIBUTING.md). -2. Check the [Good First Issues](https://github.com/SKaiNET-developers/SKaiNET/labels/good%20first%20issue). -3. Open a discussion or issue on [GitHub](https://github.com/SKaiNET-developers/SKaiNET/issues). +1. Read [Getting started as a contributor](https://skainet-developers.github.io/SKaiNET/skainet/contributing/getting-started.html) (five minutes), then the [Contribution Guide](CONTRIBUTING.md) when you need the procedure. +2. Pick an [open good first issue](https://github.com/SKaiNET-developers/SKaiNET/issues?q=is%3Aopen+label%3A%22good+first+issue%22) — every one names the file to copy the pattern from and the exact Gradle task to run. Comment on it to claim it. +3. Open a discussion or issue on [GitHub](https://github.com/SKaiNET-developers/SKaiNET/issues); the issue chooser has templates for DARC features, lane tasks and SKEEP proposals. Browse the full codebase documentation on [DeepWiki](https://deepwiki.com/SKaiNET-developers/SKaiNET). diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc index 1723bb7c3..af2063dc2 100644 --- a/docs/modules/ROOT/nav.adoc +++ b/docs/modules/ROOT/nav.adoc @@ -53,7 +53,10 @@ .Contributing * xref:contributing/index.adoc[Audience and scope] +* xref:contributing/getting-started.adoc[Getting started as a contributor] * xref:contributing/darc-workflow.adoc[DARC: advanced contribution workflow] +* xref:contributing/darc-worked-example-f1score.adoc[Worked example: F1Score via DARC] +* xref:contributing/issue-taxonomy.adoc[Issue taxonomy] * xref:contributing/build-from-source.adoc[Build from source] * xref:contributing/dtype-model.adoc[The SKaiNET dtype model] * xref:contributing/benchmarks.adoc[Engine benchmark program] diff --git a/docs/modules/ROOT/pages/contributing/darc-worked-example-f1score.adoc b/docs/modules/ROOT/pages/contributing/darc-worked-example-f1score.adoc new file mode 100644 index 000000000..7a30d371f --- /dev/null +++ b/docs/modules/ROOT/pages/contributing/darc-worked-example-f1score.adoc @@ -0,0 +1,295 @@ += Worked example: F1Score via DARC +:description: One real feature — Precision, Recall and F1Score metrics — taken through Document / Assess / Research / Code, decomposed into lane sub-issues, including the point where it hands a question over to SKEEP. + +[NOTE] +==== +**Audience: contributors who have read +xref:contributing/getting-started.adoc[Getting started] and want to see +what the process looks like on a concrete feature** — and maintainers +opening a new feature who want a template to copy. The live issues are +https://github.com/SKaiNET-developers/SKaiNET/issues/1222[#1222] (parent) +and its sub-issues; this page explains the reasoning behind them. +==== + +This is an exercise, not a spec: what actually happens when someone +picks "F1Score" off the missing-metrics list and takes it through +DARC end to end. It surfaces friction the abstract process description +does not show — most importantly, that the ground-truth harness cannot +validate a stateful metric, and that resolving *that* is a SKEEP, not a +sub-issue. + +== The decision Assess makes before any code + +`sk.ainet.lang.nn.metrics` ships `Accuracy` only. F1 depends on +precision and recall, which are *also* missing. Three classes each +re-deriving true-positive / false-positive / false-negative counts +would triplicate the iteration logic `Accuracy.kt` already shows is +non-trivial (dtype dispatch, hard vs. soft targets, argmax over `dim`, +binary threshold). The Assess-phase decision, recorded on the parent +issue: + +> Extract `Accuracy`'s helpers once, build one internal +> `ConfusionMatrixAccumulator`, and let `Precision`, `Recall` and +> `F1Score` be three `compute()` formulas over it. Ship all three from +> one DARC cycle — same data, three views. + +That is what Assess is for: not "is this hard?" but "what is the shape +of the solution, and does it avoid an obvious duplication trap?" + +.DARC or SKEEP? +**** +`Precision`, `Recall`, `F1Score` are additive classes behind the +existing `Metric` interface. No public-API *shape* change, no DSL, no +storage, no compiler footprint — **DARC alone**. Saying so explicitly +on the parent issue matters, because one part of this feature *does* +trip a SKEEP trigger (see <>), and the distinction has to +be visible to whoever picks up that lane. +**** + +== D — Document + +The parent issue, +https://github.com/SKaiNET-developers/SKaiNET/issues/1222[#1222], is +the `darc_feature_request.md` template filled in: problem, summary, +the Assess decision above, risks, research tasks, open questions, and +— the addition the lane model asks for — a *lane breakdown table* +saying which lanes apply, which are skipped, and why. + +== A — Assess + +Feasibility is not the risk; semantics are: + +* *Zero-division.* F1 is undefined when TP+FP+FN = 0 for a class. + `sklearn` returns 0.0 with a warning; `torchmetrics` takes a + `zero_division` parameter. Pick one and document it — never an + unspecified `NaN`. +* *Averaging.* Macro (unweighted mean over classes), micro (pooled + counts), per-class (no reduction) give different numbers for the same + predictions. A cross-check falls out of the definitions: for + single-label multi-class via argmax, *micro precision = micro recall = + accuracy*. That is a test. +* *Which classes does "macro" average over?* `sklearn` uses the union + of labels present in targets ∪ predictions; averaging over the full + class dimension counts never-seen classes as F1 = 0. Different + numbers on small batches. +* *Ground-truth validation has no home* — a process risk, resolved + before Code starts, not discovered inside it. See <>. + +== R — Research + +Research is its own lane +(https://github.com/SKaiNET-developers/SKaiNET/issues/1224[#1224], +`skill:numerics`, `good first issue`) precisely because it needs +PyTorch/scikit-learn literacy and *no Kotlin*. The deliverable is a +comment on the parent: an edge-case table (empty batch, unseen class, +all-wrong, ties) for both reference libraries, and three one-paragraph +recommendations with links a reviewer can click. The Kotlin lanes start +from that fixed contract. + +The hand-computed fixture every later test uses — predicted classes +`[2, 0, 1, 2]` against targets `[2, 1, 1, 0]` — is also pinned here: +per-class precision `[0, 1, 0.5]`, recall `[0, 0.5, 1]`, F1 +`[0, 0.667, 0.667]`; macro P = R = 0.5, macro F1 = 0.444; micro +P = R = F1 = 0.5 = accuracy. The research lane confirms it by actually +running `precision_recall_fscore_support`, turning a hand calculation +into a citable reference. + +[[lanes]] +== C — Code, as lanes + +The Code phase is where the lane decomposition earns its keep. Instead +of one `size:m` "implement the metrics" task, the work is a chain of +small, independently claimable sub-issues, each with one skill label +and an honest size: + +[cols="3,1,1,1,3",options="header"] +|=== +| Sub-issue | Skill | Size | Entry point | Why it is its own task + +| https://github.com/SKaiNET-developers/SKaiNET/issues/1225[#1225] +Extract `Accuracy`'s class-matching helpers into a shared internal file +| `kotlin-core` | s | ✅ first issue +| A pure refactor with existing tests as the safety net. Without it, +three near-identical argmax/iteration copies. + +| https://github.com/SKaiNET-developers/SKaiNET/issues/1226[#1226] +`Averaging` enum + internal `ConfusionMatrixAccumulator` +| `kotlin-core` | s | +| The one design-bearing task; needs the research contract. Not a +first issue. + +| https://github.com/SKaiNET-developers/SKaiNET/issues/1227[#1227] `Precision` +| `kotlin-core` | s | ✅ first issue +| One formula over the accumulator, plus tests against the fixture. + +| https://github.com/SKaiNET-developers/SKaiNET/issues/1228[#1228] `Recall` +| `kotlin-core` | s | ✅ first issue +| Twin of `Precision` — filed separately so two people can each ship one. + +| https://github.com/SKaiNET-developers/SKaiNET/issues/1229[#1229] `F1Score` +| `kotlin-core` | s | ✅ first issue +| Composes the formulas over *one* accumulator; carries the +mean-of-F1s vs. F1-of-means trap as an explicit test. + +| https://github.com/SKaiNET-developers/SKaiNET/issues/1230[#1230] +Android parity — and enable host tests for `skainet-backend-cpu` +| `android` | s | ✅ first issue +| Turned out not to be a pure smoke check: the module has no Android +host-test task today. A real finding, sized honestly. + +| https://github.com/SKaiNET-developers/SKaiNET/issues/1231[#1231] iOS · +https://github.com/SKaiNET-developers/SKaiNET/issues/1232[#1232] JS/Wasm · +https://github.com/SKaiNET-developers/SKaiNET/issues/1233[#1233] Native +| `ios` / `js` / `native` | xs | ✅ first issue +| Run one Gradle task, compare numbers, report. The easiest entry +points into the project for someone who has never seen a tensor. + +| https://github.com/SKaiNET-developers/SKaiNET/issues/1234[#1234] +Doc partials (`math` / `intuition` / `examples` / `references`) +| `docs` | s | ✅ first issue +| `math`, `intuition`, `references` can start on day one; `examples` +waits for `F1Score` to exist so the code actually runs. + +| https://github.com/SKaiNET-developers/SKaiNET/issues/1235[#1235] +DARC review and `@DarcValidated` +| `review` | s | +| Must not be any of the implementers. Walks all four phases against +the shipped code. +|=== + +Lane 4 (ground-truth / CI) is deliberately *not* a sub-issue — see the +next section. + +=== The Kotlin skeleton + +`Accuracy.kt`'s pattern, generalised. The accumulator is the only +place that walks tensors; the metrics are formulas. + +[source,kotlin] +---- +public enum class Averaging { BINARY, MACRO, MICRO } + +internal class ConfusionMatrixAccumulator(dim: Int = -1, threshold: Float? = null) { + private val tp = mutableMapOf() + private val fp = mutableMapOf() + private val fn = mutableMapOf() + + fun update(predictions: Tensor, targets: Tensor) = + forEachPrediction(predictions, targets, dim, threshold) { predicted, target -> + if (predicted == target) tp[target] = (tp[target] ?: 0) + 1 + else { fp[predicted] = (fp[predicted] ?: 0) + 1; fn[target] = (fn[target] ?: 0) + 1 } + } + + fun counts(): Map = /* union of keys -> ClassCounts(tp, fp, fn) */ + fun reset() { tp.clear(); fp.clear(); fn.clear() } +} + +public class F1Score( + private val averaging: Averaging = Averaging.MACRO, + dim: Int = -1, + threshold: Float? = null, +) : Metric { + override val name: String = "f1" + private val acc = ConfusionMatrixAccumulator(dim, threshold) + + override fun update(predictions: Tensor, targets: Tensor, ctx: ExecutionContext) = + acc.update(predictions, targets) + + override fun compute(): Double = reduce(acc.counts(), averaging) { tp, fp, fn -> + if (2 * tp + fp + fn == 0L) 0.0 else 2.0 * tp / (2 * tp + fp + fn) + } + + override fun reset() = acc.reset() +} +---- + +`forEachPrediction` is what #1225 extracts from `Accuracy`; `reduce` +(#1226) implements the three averaging modes and the zero-division +convention in one place. + +[[harness-gap]] +== The ground-truth gap — where DARC hands over to SKEEP + +`OperationExecutor` in `skainet-test-groundtruth` maps a test case's +operation name to one stateless `TensorOps` call and returns one +`Tensor`. That is the right shape for `matmul`, `conv2d`, +`relu`. It is not the shape of a `Metric`: `update()` across many +batches, `compute()` returning a scalar `Double`, `reset()`. There is no +`MetricExecutor` anywhere in the harness. + +Two honest options: + +[cols="1,2,2",options="header"] +|=== +| | A — unit tests only for v1 | B — extend the harness first + +| What +| Hand-computed fixtures, cross-checked once against `sklearn` by a +human, not wired into CI. +| A `GroundTruthMetricCase` and `MetricExecutor` beside +`OperationExecutor`, plus a Python-side fixture format. + +| Cost +| Ships this cycle. The metrics never earn the "✅ ground-truth +validated" badge ops get. +| Blocks three straightforward classes on a test-architecture change. + +| Process +| DARC, stated explicitly as a reviewed trade-off in every PR. +| A *runtime / test-integration pattern every future metric inherits* +— a **SKEEP trigger**. +|=== + +The call: **ship via A now; track B as its own SKEEP.** Blocking the +metrics on a harness redesign is the wrong trade-off; silently +accepting "unit-tested only" forever is too; and improvising the +harness extension inside whichever PR gets there first — instead of +deciding its shape once, durably — is exactly the drift SKEEP exists +to prevent. So: + +* every Code-lane PR states "unit tests only; ground-truth wiring + tracked in SKEEP-005"; +* https://github.com/SKaiNET-developers/SKaiNET/issues/1223[#1223] is + the SKEEP-005 tracking issue (`skeep`, `skill:design`), and writing + the proposal is its Lane 0 task; +* the proposal, not the parent issue, carries the "why this shape" + argument — fixture format, one batch vs. a sequence, scalar tolerance. + +== Doc partial and review sign-off + +The docs lane produces +`docs/modules/ROOT/partials/ops/metrics/{precision,recall,f1score}.adoc` +with the same four tags as +`partials/ops/tensorops/matmul.adoc`. Metrics are not `TensorOps` +functions, so the operator-doc generator will not pick the partials up; +they are surfaced from the +xref:how-to/metrics-and-perf-testing.adoc[metrics how-to] with +`include::partial$…[tag=…]`. + +After review, the reviewer — not the implementer — annotates: + +[source,kotlin] +---- +@DarcValidated(by = "First Last ", on = "2026-09-30") +override fun compute(): Double { /* … */ } +---- + +One precedent worth flagging rather than solving: `@DarcValidated` +targets functions and the KSP processor reads it only off `TensorOps` +today, so the badge is recorded in source but renders on no generated +page for a `Metric`. That is a follow-up for the doc-pipeline owner, +noted in #1235, not a blocker. + +== What this example teaches + +* *Assess decides the shape, Code executes it.* The shared-accumulator + decision is one sentence on the parent, and it turns three + medium-sized tasks into six small ones. +* *Research is a lane, not a preamble.* Somebody who reads sklearn + source and writes no Kotlin has a real, closed-ended contribution. +* *"Check on Android" was not a smoke check.* Sizing a lane honestly + means actually looking; the Android host-test gap is a finding in its + own right. +* *A DARC feature can trip a SKEEP trigger part-way.* Split the + question out, state the trade-off in every affected PR, and let the + SKEEP carry the design argument. diff --git a/docs/modules/ROOT/pages/contributing/darc-workflow.adoc b/docs/modules/ROOT/pages/contributing/darc-workflow.adoc index d552fb2e3..a31b51a62 100644 --- a/docs/modules/ROOT/pages/contributing/darc-workflow.adoc +++ b/docs/modules/ROOT/pages/contributing/darc-workflow.adoc @@ -88,6 +88,63 @@ months from now would want to know *why* the change is shaped the way it is. If yes, the work belongs in DARC; if the diff speaks for itself, it doesn't. +[[darc-and-skeep]] +== DARC and SKEEP + +SKaiNET has a second process, xref:skeep:index.adoc[SKEEP] (SKaiNET +Evolution and Enhancement Process), and the two trigger lists overlap +enough to confuse. The distinction: + +[cols="1,2,2",options="header"] +|=== +| | DARC | SKEEP + +| Unit of work +| One feature: an operator, layer, module, metric, format reader, +backend, kernel strategy. +| One architectural decision: public API shape, DSL syntax or +semantics, tensor dtype/shape/storage/execution behaviour, +compiler/runtime integration, compatibility policy. + +| Artefact +| Feature issue + lane sub-issues; for operators, a doc partial and +`@DarcValidated`. +| A numbered proposal in `docs/modules/skeep/pages/` with a fixed +section set and a `Tracking issue:` header. + +| Lifecycle +| ✖ → ⚠ → ✅ (`@DarcValidated`) +| `Draft` → `Accepted` → `Implemented` / `Superseded` / `Rejected` + +| Sign-off +| A reviewer who is not the implementer. +| A maintainer, by moving the status field. +|=== + +*Rule of thumb:* if the change trips a SKEEP trigger, write the SKEEP +first and let the DARC feature issue's Document section link to it. +DARC decides whether and how to build *a thing*; SKEEP decides the +*shape of the codebase* the thing lands in. When a DARC feature runs +into a SKEEP-sized question mid-way — the +xref:contributing/darc-worked-example-f1score.adoc[F1Score worked +example] does, in its ground-truth lane — split the question out into +its own SKEEP rather than settling it inside a sub-issue. The full +decision list is in +xref:contributing/getting-started.adoc#darc-or-skeep[Getting started +→ DARC or SKEEP?]. + +== Decomposing a DARC feature into lanes + +The four phases say *what* has to happen; they do not say *who* can +do it. A DARC feature issue is therefore split into lane sub-issues — +numerics research, Kotlin core, per-platform verification, +ground-truth wiring, docs, review — each labelled with the single +skill it needs and an honest size, so the backlog is filterable by +"things I can do" across all features. The lanes, labels, and +templates are defined in +xref:contributing/issue-taxonomy.adoc[Issue taxonomy]; the parent +issue's Document section states which lanes apply to *this* feature. + == Specialisation: operator documentation Operator documentation is the largest single application of DARC in @@ -215,6 +272,19 @@ its outcomes are encoded. | Issue template for new DARC proposals. Section headers match the four phases: Document / Assess / Research / Code. +| `.github/ISSUE_TEMPLATE/darc_lane_task.md` +| Issue template for one lane of a DARC feature, opened as a native +sub-issue of the parent. Labelled by skill, size, and phase — see +xref:contributing/issue-taxonomy.adoc[Issue taxonomy]. + +| `.github/labels.txt`, `.github/scripts/sync-labels.sh` +| The label taxonomy the lane sub-issues use, and the script that +applies it. + +| xref:contributing/darc-worked-example-f1score.adoc[Worked example: F1Score via DARC] +| One feature taken through all four phases, its lane breakdown, and +the point where it hands a question over to SKEEP. + | `skainet-lang-ksp-annotations/.../DarcValidated.kt` | The annotation that records a passed DARC review on an operator function. `SOURCE` retention, `FUNCTION` target. diff --git a/docs/modules/ROOT/pages/contributing/getting-started.adoc b/docs/modules/ROOT/pages/contributing/getting-started.adoc new file mode 100644 index 000000000..2206601c3 --- /dev/null +++ b/docs/modules/ROOT/pages/contributing/getting-started.adoc @@ -0,0 +1,250 @@ += Getting started as a contributor +:description: The two workflows SKaiNET uses for non-trivial work — DARC for features and SKEEP for durable design decisions — and how to find, claim, and finish a first task. + +[NOTE] +==== +**Audience: anyone who wants to change SKaiNET itself** — from a first +`good first issue` to a maintainer opening a new feature. Library +*consumers* do not need this page; see xref:using/index.adoc[Using SKaiNET]. +==== + +SKaiNET has two contribution processes. They are not alternatives and +they are not a hierarchy; they answer two different questions: + +[cols="1,2,2",options="header"] +|=== +| | DARC | SKEEP + +| *The question it answers* +| "Is this the right thing to build, and is it built to the documented +design?" — for *one feature*: an operator, a metric, a layer, a format +reader, a kernel strategy. +| "What is the durable shape of *this codebase* going forward?" — for +*one architectural decision*: a public API, a DSL syntax, a storage +model, a runtime/compiler integration point, a compatibility policy. + +| *Expansion* +| **D**ocument · **A**ssess · **R**esearch · **C**ode. Cyclical; +the prose is the deliverable that survives, the code follows. +| **SK**aiNET **E**volution and **E**nhancement **P**rocess. +Numbered, KEEP-style proposals with a fixed section set. + +| *Artefact* +| A GitHub issue from the +https://github.com/SKaiNET-developers/SKaiNET/blob/develop/.github/ISSUE_TEMPLATE/darc_feature_request.md[DARC +Feature Proposal] template, decomposed into lane sub-issues; for +operators, a doc partial plus the `@DarcValidated` annotation. +| A file `docs/modules/skeep/pages/NNN-short-title.adoc` with a +`Tracking issue:` header pointing at a GitHub issue from the +https://github.com/SKaiNET-developers/SKaiNET/blob/develop/.github/ISSUE_TEMPLATE/skeep_tracking.md[SKEEP +Proposal] template. + +| *Lifecycle* +| ✖ undocumented → ⚠ prose but unreviewed → ✅ `@DarcValidated` +| `Draft` → `Accepted` → `Implemented` (or `Superseded` / `Rejected`) + +| *Who signs off* +| A reviewer who is *not* the implementer. +| A maintainer, by moving the `Status:` field. + +| *Authoritative page* +| xref:contributing/darc-workflow.adoc[DARC: advanced contribution workflow] +| xref:skeep:index.adoc[SKEEP: SKaiNET Evolution and Enhancement Process] +|=== + +[[darc-or-skeep]] +== DARC or SKEEP? + +Most contributions need neither. A typo, an obvious one-line bug, a +dependency bump, a missing test for shipped behaviour — just open a +PR (see xref:contributing/darc-workflow.adoc#_when_darc_applies[when DARC applies]). + +For anything larger, run down this list in order: + +. *Does the change trip a SKEEP trigger?* Public Kotlin API shape, DSL + syntax or semantics, tensor dtype/shape/storage/execution behaviour, + compiler/graph-export/runtime integration, compatibility or + migration policy, or the docs structure of a long-lived feature + area. If yes, **SKEEP first** — the proposal carries the "why this + shape" argument, and every later DARC feature links to it instead + of re-litigating the design. +. *Otherwise, would a maintainer six months from now want to know why + the change is shaped the way it is?* If yes, **DARC**: open a + feature issue, decompose it into lanes, ship through the four + phases. +. *Both?* A new op that also needs a new architectural pattern to + support it — write the SKEEP, then have the DARC issue's Document + section link to it. Never fold a SKEEP-sized decision into a DARC + sub-issue "as an afterthought"; that is precisely the drift SKEEP + exists to prevent. + +.Concrete calls +==== +* `Precision`, `Recall`, `F1Score` metrics — additive classes behind the + existing `Metric` interface, no API/DSL/storage footprint: **DARC only** + (see xref:contributing/darc-worked-example-f1score.adoc[the worked example]). +* Teaching the ground-truth harness to validate *stateful* metrics, not + just stateless tensor ops — a test-integration pattern every future + metric inherits: **SKEEP**. +* A new tensor-storage model, a new tensor literal syntax: **SKEEP** + (SKEEP-003, SKEEP-001 respectively). +* A new SIMD kernel for an existing op: **DARC**. +==== + +If you are still unsure, open a small draft SKEEP or ask on the issue +*before* implementing the whole feature — the project's default is to +lean toward writing the record, not skipping it. + +== How a feature is broken up: lanes + +A DARC feature issue is one parent (the Document/Assess/Research +artefact) plus one *sub-issue per lane*. Lanes split the work by the +skill it needs, so an Android developer who has never seen the tensor +internals, or a PyTorch person who does not write Kotlin, can each pick +up something real. + +[cols="1,2,3,1",options="header"] +|=== +| Lane | Skill | Produces | Size + +| *0 · Design (SKEEP)* +| `skill:design` +| A numbered proposal under `docs/modules/skeep/pages/`, `Status: Draft`, +with a tracking issue. Only when a SKEEP trigger applies — and then +*before* lane 2 starts. +| M–L + +| *1 · Numerics / Research* +| `skill:numerics` +| Formula, citations, and an edge-case catalogue (zero-division, +empty batch, dtype boundaries) from PyTorch / NumPy / scikit-learn. +No Kotlin required. +| S–M + +| *2 · Kotlin core* +| `skill:kotlin-core` +| The `commonMain` implementation, following an existing sibling's +pattern. +| M–L + +| *3 · Platform verification* +| `skill:android` / `skill:ios` / `skill:js` / `skill:native` +| Confirmation the code builds and gives identical results on that +target — or, for ops needing a hand-written kernel, the kernel itself. +| XS–L (say which!) + +| *4 · Ground-truth / CI* +| `skill:kotlin-core` + Docker +| A wired ground-truth test case, or a scoped proposal when the op +doesn't fit the harness's shape. +| S–M + +| *5 · Docs / DARC* +| `skill:docs` +| The doc partial (`math` / `intuition` / `examples` / `references` tags). +| S + +| *6 · Review* +| `skill:review` +| DARC sign-off and the `@DarcValidated` annotation. Must not be the +implementer. +| S +|=== + +Not every feature needs every lane. Whoever opens the parent issue says +which lanes are substantive and which are trivial *for this feature* — +that is part of the Document artefact. Lane 3 in particular ranges from +"run the existing test suite on iOS" (an hour) to "write a NEON JNI +kernel" (days); the sub-issue must say which. + +== Finding something to do + +The pinned issue +https://github.com/SKaiNET-developers/SKaiNET/issues/1237[👋 New +contributors: start here] at the top of the issue list has the searches +below as one-click links; it is the fastest route in. + +Every sub-issue carries one `skill:*` label, one `size:*` label, and a +DARC phase label, so the whole backlog is filterable — not just one +feature's checklist. Useful searches on the +https://github.com/SKaiNET-developers/SKaiNET/issues[issue tracker]: + +[cols="2,3"] +|=== +| `is:open label:"good first issue"` +| No prior SKaiNET codebase knowledge assumed. + +| `is:open label:skill:android label:size:xs` +| Everything an Android developer can finish in under an hour, across +all features. + +| `is:open label:skill:numerics` +| Research tasks — reading PyTorch/sklearn, writing up conventions — +no Kotlin at all. + +| `is:open label:tracking label:darc` +| The parent feature issues, to see the big picture. + +| `is:open label:skeep` +| Design proposals looking for review. +|=== + +To claim a task, comment on the sub-issue. A maintainer assigns it; +if a task has been claimed but idle for two weeks, it is fair to ask +whether it is free again. + +The full label reference, title conventions, and the `gh` commands +maintainers use to decompose a feature are in +xref:contributing/issue-taxonomy.adoc[Issue taxonomy]. + +== Your first pull request + +. xref:contributing/build-from-source.adoc[Build from source] and run + the module's tests once before touching anything. +. Branch from `develop` following Gitflow (`GITFLOW.adoc` in the repo + root): `feature/-short-title`. +. Keep the PR scoped to its sub-issue. "While I was in there" changes + go into their own sub-issue — the lane decomposition only works if + PRs stay small enough for a lane-specific reviewer. +. Link the sub-issue in the PR description (`Closes #NNN`) and say which + DARC phase it delivers. For Code-lane PRs, state explicitly what was + *not* validated (for example "unit tests only, ground-truth wiring is + tracked in SKEEP-NNN") — a reviewed, stated trade-off is fine; a + silent gap is not. +. CI must be green: KtLint / Detekt, unit tests, and the binary + compatibility validator for public API changes. +. Report back on the parent issue when the sub-issue closes, so the + parent's lane rollup stays honest. + +== Where things live + +[cols="2,3",options="header"] +|=== +| Location | Role + +| xref:contributing/darc-workflow.adoc[DARC: advanced contribution workflow] +| Authoritative definition of the four phases, when they apply, the +operator-doc specialisation and `@DarcValidated`. + +| xref:skeep:index.adoc[SKEEP index] +| Status values, proposal template, and the list of current proposals. + +| xref:contributing/issue-taxonomy.adoc[Issue taxonomy] +| Labels, title conventions, templates, and how to decompose a +feature into sub-issues. + +| xref:contributing/darc-worked-example-f1score.adoc[Worked example: F1Score via DARC] +| One real feature taken through DARC end to end, including the point +where it runs into a SKEEP trigger. + +| `CONTRIBUTING.md`, `GITFLOW.adoc` (repo root) +| SKEEP authoring procedure, branch policy, commit conventions. + +| `.github/ISSUE_TEMPLATE/` +| `darc_feature_request.md` (parent), `darc_lane_task.md` +(sub-issue), `skeep_tracking.md` (SKEEP tracking issue). + +| `.github/labels.txt`, `.github/scripts/sync-labels.sh` +| Source of truth for the label taxonomy and the script that applies +it. +|=== diff --git a/docs/modules/ROOT/pages/contributing/index.adoc b/docs/modules/ROOT/pages/contributing/index.adoc index ac34c81df..a860e59fb 100644 --- a/docs/modules/ROOT/pages/contributing/index.adoc +++ b/docs/modules/ROOT/pages/contributing/index.adoc @@ -22,6 +22,11 @@ The Contributing section is for the engineer who: - Adds or replaces kernels in the CPU backend (scalar, Panama Vector, the native FFM provider). - Operates the self-hosted runner that publishes benchmark results. +- Picks up a first task from the skill-labelled issue backlog, or + files one for others to pick up. +- Takes a feature through the + xref:contributing/darc-workflow.adoc[DARC workflow] (Document / + Assess / Research / Code). - Drafts or reviews durable API and architecture proposals in the xref:skeep:index.adoc[SKEEP proposal track]. @@ -30,6 +35,10 @@ Concretely: [cols="2,3",options="header"] |=== | Page | What it answers +| xref:contributing/getting-started.adoc[Getting started as a contributor] | **Start here.** The two workflows (DARC for features, SKEEP for design decisions), when each applies, how work is split into skill lanes, and how to find and claim a first task. +| xref:contributing/darc-workflow.adoc[DARC: advanced contribution workflow] | The four phases, when DARC applies, the operator-documentation specialisation and the `@DarcValidated` badge. +| xref:contributing/darc-worked-example-f1score.adoc[Worked example: F1Score via DARC] | One feature taken through all four phases and decomposed into lane sub-issues, including where it hands a question over to SKEEP. +| xref:contributing/issue-taxonomy.adoc[Issue taxonomy] | Every label, title prefix, and issue template, plus the `gh` commands to decompose a feature into sub-issues. | xref:contributing/build-from-source.adoc[Build from source] | How to clone, build, and run the test suite locally. | xref:contributing/benchmarks.adoc[Engine benchmark program] | The Phoronix Test Suite / OpenBenchmarking publication path: methodology, manifest, lanes, CI workflow, replay, and the gated steps to actually publish a run to OpenBenchmarking.org. | xref:contributing/matmul-kernels.adoc[Reading the matmul benchmark] | What the published numbers mean — scalar vs. Panama vs. quantized regimes, roofline reasoning, and a checklist for interpreting any new measurement. @@ -45,11 +54,12 @@ Concretely: - **What an operator does or how the public DSL works.** See xref:reference/operators/generated/index.adoc[Operator reference] and the Explanation pages. -- **Issue reports, branch policy, commit conventions, or the detailed - SKEEP authoring procedure.** Those live in repo-root files: - `CONTRIBUTING.md`, `GITFLOW.adoc`, - `CHANGELOG.md`, `FAQ.md`. The published docs site intentionally - does not duplicate them; they belong with the code. +- **Branch policy, commit conventions, or the detailed SKEEP authoring + procedure.** Those live in repo-root files: `CONTRIBUTING.md`, + `GITFLOW.adoc`, `CHANGELOG.md`, `FAQ.md`. The published docs site + intentionally does not duplicate them; they belong with the code. + (`CONTRIBUTING.md` carries a short summary of the DARC/SKEEP split + and the issue taxonomy and links back here for the full text.) == Conventions used in this section diff --git a/docs/modules/ROOT/pages/contributing/issue-taxonomy.adoc b/docs/modules/ROOT/pages/contributing/issue-taxonomy.adoc new file mode 100644 index 000000000..15d560834 --- /dev/null +++ b/docs/modules/ROOT/pages/contributing/issue-taxonomy.adoc @@ -0,0 +1,224 @@ += Issue taxonomy +:description: What every SKaiNET issue label, title prefix, and issue template means, and how a DARC feature is decomposed into skill-labelled sub-issues. + +[NOTE] +==== +**Audience: contributors looking for work, and maintainers filing it.** +Read xref:contributing/getting-started.adoc[Getting started as a +contributor] first for *why* issues are structured this way; this page +is the reference for *what each label and prefix means*. +==== + +The source of truth for labels is `.github/labels.txt` in the repo; +`.github/scripts/sync-labels.sh` applies it idempotently with `gh`. +If this page and that file disagree, the file wins — fix the page. + +== Structure of a feature + +[mermaid] +.... +flowchart TD + P["Parent issue
darc_feature_request.md
labels: tracking · darc · enhancement
= the Document / Assess / Research artefact"] + P --> L1["[Lane 1 · numerics] …
sub-issue · research · skill:numerics · size:s"] + P --> L2["[Lane 2 · kotlin-core] …
sub-issue · coding · skill:kotlin-core · size:m"] + P --> L3["[Lane 3 · android] …
sub-issue · coding · skill:android · size:xs"] + P --> L5["[Lane 5 · docs] …
sub-issue · documentation · skill:docs · size:s"] + P --> L6["[Lane 6 · review] …
sub-issue · assessment · skill:review · size:s"] + S["SKEEP tracking issue
skeep_tracking.md
labels: skeep · tracking"] -. "linked from Document
section when a trigger applies" .-> P +.... + +* **One parent per feature.** It is the DARC Document/Assess/Research + artefact and never gets assigned to one person — the lanes do. +* **One sub-issue per substantive lane**, created as a GitHub native + sub-issue of the parent so the parent shows a progress rollup. +* **A SKEEP is its own tracking issue**, never a sub-issue of a feature + — a design record outlives the feature that first needed it. + +== Labels + +Four dimensions. A sub-issue carries exactly one label from each of +the *phase*, *skill*, and *size* groups, plus whichever structure and +entry-point labels apply. + +=== Structure + +[cols="1,3",options="header"] +|=== +| Label | Meaning +| `tracking` | Parent issue with sub-issues. Never assigned to a person. +| `sub-issue` | One lane of a parent. Assignable, closeable on its own. +| `darc` | The parent is a DARC feature (Document / Assess / Research / Code). +| `skeep` | Tracking issue for a numbered SKEEP proposal. +|=== + +=== DARC phase + +Which phase of the workflow the task delivers. These labels pre-date +the lane model and are reused as-is. + +[cols="1,1,3",options="header"] +|=== +| Label | Phase | Typical lane +| `documentation` | D — Document | Lane 5 (doc partials), or the parent itself +| `assessment` | A — Assess | Lane 6 (review), ground-truth decisions +| `research` | R — Research | Lane 1 (numerics), Lane 0 (SKEEP authorship) +| `coding` | C — Code | Lanes 2, 3, 4 +|=== + +=== Skill + +What a contributor must already know. Exactly one per sub-issue; if +a task genuinely needs two skills, it is two tasks. + +[cols="1,3",options="header"] +|=== +| Label | Meaning +| `skill:numerics` | PyTorch / NumPy / scikit-learn / math background. No Kotlin required. +| `skill:kotlin-core` | Kotlin implementation in `commonMain`. +| `skill:android` | Android target, build, or kernel (JNI / NEON) work. +| `skill:ios` | iOS / Kotlin-Native-Apple target work. +| `skill:native` | Kotlin/Native (Linux, macOS) or FFM kernel work. +| `skill:js` | JS / Wasm target work. +| `skill:docs` | AsciiDoc / technical writing. +| `skill:review` | DARC review. Must **not** be the task's implementer. +| `skill:design` | SKEEP authorship: architectural / API-shape judgement. +|=== + +=== Size + +An honest wall-clock estimate for someone who has the listed skill. +Not story points: if a task is `size:l`, it probably needs its own +design discussion before it is a task at all. + +[cols="1,3",options="header"] +|=== +| Label | Meaning +| `size:xs` | Under 1 hour. +| `size:s` | A few hours. +| `size:m` | 1–2 days. +| `size:l` | 3+ days; likely needs its own design discussion. +|=== + +=== Entry point + +[cols="1,3",options="header"] +|=== +| Label | Meaning +| `good first issue` | No prior SKaiNET codebase knowledge assumed. The +task says exactly which file to copy the pattern from. +| `help wanted` | Maintainers are actively looking for someone to pick +this up. +|=== + +=== Area labels + +The pre-existing area labels (`tensors`, `layers`, `training`, +`compute-backend`, `file-format:gguf`, `platform`, `quantization`, …) +say *what part of the engine* a task touches. Apply them to the parent; +copy to sub-issues when it helps filtering. They are orthogonal to the +four dimensions above and are not managed by `labels.txt`. + +== Title conventions + +[cols="2,3",options="header"] +|=== +| Pattern | Used for + +| `[Feature]: ` +| Parent DARC issue (the template pre-fills the prefix). + +| `[Lane N · ] — ` +| Lane sub-issue. The lane number and skill are visible in the issue +list without opening it; the part after the dash is the deliverable, +not the activity ("confusion-matrix accumulator", not "work on +metrics"). + +| `[SKEEP-NNN]: ` +| SKEEP tracking issue. Matches the proposal file's number. +|=== + +== Templates + +[cols="2,3",options="header"] +|=== +| Template | When + +| `.github/ISSUE_TEMPLATE/darc_feature_request.md` +| Opening a feature. The four phase sections *are* the Document +artefact. Add a "Lane breakdown" list at the end saying which lanes +apply and which are skipped for this feature. + +| `.github/ISSUE_TEMPLATE/darc_lane_task.md` +| One lane. Parent reference, skill, size, blocked-by, numbered steps, +acceptance checklist. + +| `.github/ISSUE_TEMPLATE/skeep_tracking.md` +| The coordination issue for a SKEEP proposal. The proposal text lives +in `docs/modules/skeep/pages/`; this issue tracks status upkeep and +implementation PRs. +|=== + +== Decomposing a feature with `gh` + +The parent first, then each lane as a native sub-issue: + +[source,bash] +---- +# 1. Parent (Document / Assess / Research artefact) +gh issue create \ + --title "[Feature]: Precision, Recall and F1Score metrics" \ + --label "enhancement,tracking,darc,training" \ + --body-file parent.md + +# 2. One sub-issue per lane — --parent makes it a native sub-issue +gh issue create --parent <parent-number> \ + --title "[Lane 1 · numerics] Precision/Recall/F1 — averaging-mode and zero-division conventions" \ + --label "sub-issue,research,skill:numerics,size:s,good first issue" \ + --body-file lane-1.md + +gh issue create --parent <parent-number> \ + --title "[Lane 2 · kotlin-core] Precision/Recall/F1 — shared ConfusionMatrixAccumulator" \ + --label "sub-issue,coding,skill:kotlin-core,size:s" \ + --body-file lane-2.md +---- + +Sub-issues can also be attached after the fact from the parent's +"Create sub-issue" / "Add existing issue" button in the GitHub UI. + +== Keeping labels in sync + +[source,bash] +---- +# Dry run: print the gh commands without executing them +DRY_RUN=1 .github/scripts/sync-labels.sh + +# Apply to the current repo (needs triage permission) +.github/scripts/sync-labels.sh + +# Apply to a fork or a sibling repo +.github/scripts/sync-labels.sh -R SKaiNET-developers/SKaiNET-transformers +---- + +The script only creates and updates; it never deletes a label that is +not in `labels.txt`. To add a label, add a line to the file and re-run. + +== Lane task anatomy + +A good sub-issue answers, in this order, without the reader opening +anything else: + +. *Sub-issue of which parent*, so the big picture is one click away. +. *Skill needed* — and, equally, what is *not* needed ("no tensor + internals knowledge required"). +. *Size*, honestly, and *blocked by* which other sub-issue if any. +. *Numbered steps* naming the sibling file to copy the pattern from + (`nn/metrics/Accuracy.kt`), the exact Gradle task to run + (`:skainet-backends:skainet-backend-cpu:jvmTest`), and where to + report the result. +. *Acceptance* as observable outcomes a reviewer can check. +. *What to do if it goes sideways*: "open a specific bug, don't block + the parent on investigation here". + +The two lane tasks in the F1Score worked example +(xref:contributing/darc-worked-example-f1score.adoc#lanes[lane +breakdown]) are the reference shape. diff --git a/docs/modules/ROOT/pages/index.adoc b/docs/modules/ROOT/pages/index.adoc index 81a609613..cbf0cdd89 100644 --- a/docs/modules/ROOT/pages/index.adoc +++ b/docs/modules/ROOT/pages/index.adoc @@ -29,7 +29,11 @@ the tiny dry-run sample, then run the real-runtime profile, inspect + You're modifying SKaiNET itself — building from source, adding kernels, running the benchmark suite, maintaining CI, operating the -self-hosted runner. Audience admonition at the top of each page. +self-hosted runner. New here? Read +xref:contributing/getting-started.adoc[Getting started as a contributor] +first: it explains the two workflows (DARC for features, SKEEP for +design decisions) and how to find a first task. Audience admonition at +the top of each page. [NOTE] ==== diff --git a/docs/modules/skeep/nav.adoc b/docs/modules/skeep/nav.adoc index 3e3b79a87..deeb78561 100644 --- a/docs/modules/skeep/nav.adoc +++ b/docs/modules/skeep/nav.adoc @@ -5,3 +5,4 @@ ** xref:skeep:002-android-offheap-tensor-storage.adoc[SKEEP-002: Off-heap tensor storage on Android] ** xref:skeep:003-unified-tensor-storage.adoc[SKEEP-003: Unifying the tensor storage model] ** xref:skeep:003a-placement-and-planning-resolution.adoc[SKEEP-003a: Placement & planning resolution] +** xref:skeep:004-virtual-tensor-layout.adoc[SKEEP-004: Virtual tensor layout] diff --git a/docs/modules/skeep/pages/index.adoc b/docs/modules/skeep/pages/index.adoc index 0affa5476..9c0f7df04 100644 --- a/docs/modules/skeep/pages/index.adoc +++ b/docs/modules/skeep/pages/index.adoc @@ -5,6 +5,43 @@ SKEEP is SKaiNET's proposal track for design changes that need more durability t It is inspired by Kotlin KEEP, but scoped to SKaiNET. A SKEEP can describe public APIs, DSL changes, compiler or runtime integration points, storage semantics, documentation strategy, or migration policy. +== SKEEP and DARC + +SKEEP is one of two contribution processes. The other, +xref:ROOT:contributing/darc-workflow.adoc[DARC] (Document / Assess / Research +/ Code), drives *one feature* — an operator, a metric, a kernel — through +design, validation, and implementation. SKEEP records *one architectural +decision* that many features will inherit. + +Write a SKEEP when a change affects: + +* public Kotlin APIs; +* DSL syntax or semantics; +* tensor dtype, shape, storage, or execution behaviour; +* compiler, graph export, or runtime integration; +* compatibility or migration policy; +* documentation structure for a long-lived feature area. + +Do not write one for local bug fixes, internal refactors, dependency +bumps, test-only changes, or additive features that sit behind an +existing interface — those are DARC's territory, or need neither. When +both apply, the SKEEP comes first and the DARC feature issue links to +it. The full decision rule, with concrete calls, is in +xref:ROOT:contributing/getting-started.adoc#darc-or-skeep[Getting started as +a contributor → DARC or SKEEP?]. + +== Procedure + +The step-by-step authoring procedure (branch name, claiming a number, +registering in `nav.adoc` and the table below, required sections) is +in `CONTRIBUTING.md` at the repo root, next to the code it governs. +In short: branch `feature/skeep-NNN-short-title`, create +`docs/modules/skeep/pages/NNN-short-title.adoc` at `Status: Draft`, +open a tracking issue from the +https://github.com/SKaiNET-developers/SKaiNET/blob/develop/.github/ISSUE_TEMPLATE/skeep_tracking.md[SKEEP +Proposal] template, and put its link in the proposal's `Tracking +issue:` header. + == Numbering SKEEP files use three-digit stable numbering: @@ -35,6 +72,15 @@ A newer SKEEP replaced the design. Rejected:: The proposal was considered and intentionally not pursued. +[IMPORTANT] +==== +The status field is only trustworthy if someone moves it. The PR that +ships a proposal's implementation must also flip its `Status:` to +`Implemented` — do not leave that for a later reader to notice. A +maintainer moves `Draft` to `Accepted`; the implementing PR moves +`Accepted` to `Implemented`. +==== + == Proposal Template Every proposal should include: @@ -42,6 +88,9 @@ Every proposal should include: * title and SKEEP number; * status; * audience; +* created date and tracking issue (the GitHub issue that coordinates + day-to-day work; the proposal is the durable record, the issue is + the conversation); * summary; * motivation; * proposed design;